summaryrefslogtreecommitdiffstats
path: root/widget/tests/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /widget/tests/gtest
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--widget/tests/gtest/MockWinWidget.cpp77
-rw-r--r--widget/tests/gtest/MockWinWidget.h85
-rw-r--r--widget/tests/gtest/TestTimeConverter.cpp265
-rw-r--r--widget/tests/gtest/TestTouchResampler.cpp941
-rw-r--r--widget/tests/gtest/TestWinHeaderOnlyUtils.cpp37
-rw-r--r--widget/tests/gtest/TestWinMessageLoggingUtils.cpp101
-rw-r--r--widget/tests/gtest/TestWinWindowOcclusionTracker.cpp167
-rw-r--r--widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp402
-rw-r--r--widget/tests/gtest/moz.build27
9 files changed, 2102 insertions, 0 deletions
diff --git a/widget/tests/gtest/MockWinWidget.cpp b/widget/tests/gtest/MockWinWidget.cpp
new file mode 100644
index 0000000000..ed7850076f
--- /dev/null
+++ b/widget/tests/gtest/MockWinWidget.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "MockWinWidget.h"
+
+#include "mozilla/gfx/Logging.h"
+
+NS_IMPL_ISUPPORTS_INHERITED0(MockWinWidget, nsBaseWidget)
+
+// static
+RefPtr<MockWinWidget> MockWinWidget::Create(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect) {
+ RefPtr<MockWinWidget> window = new MockWinWidget;
+ if (!window->Initialize(aStyle, aExStyle, aRect)) {
+ return nullptr;
+ }
+
+ return window;
+}
+
+MockWinWidget::MockWinWidget() {}
+
+MockWinWidget::~MockWinWidget() {
+ if (mWnd) {
+ ::DestroyWindow(mWnd);
+ mWnd = 0;
+ }
+}
+
+bool MockWinWidget::Initialize(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect) {
+ WNDCLASSW wc;
+ const wchar_t className[] = L"MozillaMockWinWidget";
+ HMODULE hSelf = ::GetModuleHandle(nullptr);
+
+ if (!GetClassInfoW(hSelf, className, &wc)) {
+ ZeroMemory(&wc, sizeof(WNDCLASSW));
+ wc.hInstance = hSelf;
+ wc.lpfnWndProc = ::DefWindowProc;
+ wc.lpszClassName = className;
+ RegisterClassW(&wc);
+ }
+
+ mWnd = ::CreateWindowExW(aExStyle, className, className, aStyle, aRect.X(),
+ aRect.Y(), aRect.Width(), aRect.Height(), nullptr,
+ nullptr, hSelf, nullptr);
+ if (!mWnd) {
+ gfxCriticalNoteOnce << "GetClientRect failed " << ::GetLastError();
+ return false;
+ }
+
+ // First nccalcszie (during CreateWindow) for captioned windows is
+ // deliberately ignored so force a second one here to get the right
+ // non-client set up.
+ if (mWnd && (aStyle & WS_CAPTION)) {
+ ::SetWindowPos(mWnd, NULL, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_NOACTIVATE | SWP_NOREDRAW);
+ }
+
+ mBounds = aRect;
+ return true;
+}
+
+void MockWinWidget::NotifyOcclusionState(
+ mozilla::widget::OcclusionState aState) {
+ mCurrentState = aState;
+}
+
+nsSizeMode MockWinWidget::SizeMode() {
+ if (::IsIconic(mWnd)) {
+ return nsSizeMode_Minimized;
+ }
+ return nsSizeMode_Normal;
+}
diff --git a/widget/tests/gtest/MockWinWidget.h b/widget/tests/gtest/MockWinWidget.h
new file mode 100644
index 0000000000..891e1c942d
--- /dev/null
+++ b/widget/tests/gtest/MockWinWidget.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTEST_MockWinWidget_H
+#define GTEST_MockWinWidget_H
+
+#include <windows.h>
+
+#include "nsBaseWidget.h"
+#include "Units.h"
+
+class MockWinWidget : public nsBaseWidget {
+ public:
+ static RefPtr<MockWinWidget> Create(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override;
+
+ void SetExpectation(mozilla::widget::OcclusionState aExpectation) {
+ mExpectation = aExpectation;
+ }
+
+ bool IsExpectingCall() const { return mExpectation != mCurrentState; }
+
+ HWND GetWnd() { return mWnd; }
+
+ nsSizeMode SizeMode() override;
+ void SetSizeMode(nsSizeMode aMode) override {}
+
+ void* GetNativeData(uint32_t aDataType) override { return nullptr; }
+
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ InitData* aInitData = nullptr) override {
+ return NS_OK;
+ }
+ virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ InitData* aInitData = nullptr) override {
+ return NS_OK;
+ }
+ virtual void Show(bool aState) override {}
+ virtual bool IsVisible() const override { return true; }
+ virtual void Move(double aX, double aY) override {}
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override {}
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override {}
+
+ virtual void Enable(bool aState) override {}
+ virtual bool IsEnabled() const override { return true; }
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override {}
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override {}
+ virtual nsresult SetTitle(const nsAString& title) override { return NS_OK; }
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override {
+ return NS_OK;
+ }
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override {}
+ virtual InputContext GetInputContext() override { abort(); }
+
+ private:
+ MockWinWidget();
+ ~MockWinWidget();
+
+ bool Initialize(DWORD aStyle, DWORD aExStyle,
+ const LayoutDeviceIntRect& aRect);
+
+ HWND mWnd = 0;
+
+ mozilla::widget::OcclusionState mExpectation =
+ mozilla::widget::OcclusionState::UNKNOWN;
+ mozilla::widget::OcclusionState mCurrentState =
+ mozilla::widget::OcclusionState::UNKNOWN;
+};
+
+#endif
diff --git a/widget/tests/gtest/TestTimeConverter.cpp b/widget/tests/gtest/TestTimeConverter.cpp
new file mode 100644
index 0000000000..22cbc3f9e6
--- /dev/null
+++ b/widget/tests/gtest/TestTimeConverter.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/TimeStamp.h"
+#include "SystemTimeConverter.h"
+
+using mozilla::SystemTimeConverter;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+namespace {
+
+// This class provides a mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter. It can be constructed with a particular
+// Time and always returns that Time.
+template <typename Time>
+class MockCurrentTimeGetter {
+ public:
+ MockCurrentTimeGetter() : mTime(0) {}
+ explicit MockCurrentTimeGetter(Time aTime) : mTime(aTime) {}
+
+ // Methods needed for CurrentTimeGetter compatibility
+ Time GetCurrentTime() const { return mTime; }
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {}
+
+ private:
+ Time mTime;
+};
+
+// This is another mock implementation of the CurrentTimeGetter template
+// type used in SystemTimeConverter, except this asserts that it will not be
+// used. i.e. it should only be used in calls to SystemTimeConverter that we
+// know will not invoke it.
+template <typename Time>
+class UnusedCurrentTimeGetter {
+ public:
+ Time GetCurrentTime() const {
+ EXPECT_TRUE(false);
+ return 0;
+ }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ EXPECT_TRUE(false);
+ }
+};
+
+// This class provides a mock implementation of the TimeStampNowProvider
+// template type used in SystemTimeConverter. It also has other things in it
+// that allow the test to better control time for testing purposes.
+class MockTimeStamp {
+ public:
+ // This should generally be called at the start of every test function, as
+ // it will initialize this class's static fields to sane values. In particular
+ // it will initialize the baseline TimeStamp against which all other
+ // TimeStamps are compared.
+ static void Init() {
+ sBaseline = TimeStamp::Now();
+ sTimeStamp = sBaseline;
+ }
+
+ // Advance the timestamp returned by `MockTimeStamp::Now()`
+ static void Advance(double ms) {
+ sTimeStamp += TimeDuration::FromMilliseconds(ms);
+ }
+
+ // Returns the baseline TimeStamp, that is used as a fixed reference point
+ // in time against which other TimeStamps can be compared. This is needed
+ // because mozilla::TimeStamp itself doesn't provide any conversion to
+ // human-readable strings, and we need to convert it to a TimeDuration in
+ // order to get that. This baseline TimeStamp can be used to turn an
+ // arbitrary TimeStamp into a TimeDuration.
+ static TimeStamp Baseline() { return sBaseline; }
+
+ // This is the method needed for TimeStampNowProvider compatibility, and
+ // simulates `TimeStamp::Now()`
+ static TimeStamp Now() { return sTimeStamp; }
+
+ private:
+ static TimeStamp sTimeStamp;
+ static TimeStamp sBaseline;
+};
+
+TimeStamp MockTimeStamp::sTimeStamp;
+TimeStamp MockTimeStamp::sBaseline;
+
+// Could have platform-specific implementations of this using DWORD, guint32,
+// etc behind ifdefs. But this is sufficient for now.
+using GTestTime = uint32_t;
+using TimeConverter = SystemTimeConverter<GTestTime, MockTimeStamp>;
+
+} // namespace
+
+// Checks the expectation that the TimeStamp `ts` is exactly `ms` milliseconds
+// after the baseline timestamp. This is a macro so gtest still gives us useful
+// line numbers for failures.
+#define EXPECT_TS(ts, ms) \
+ EXPECT_EQ((ts)-MockTimeStamp::Baseline(), TimeDuration::FromMilliseconds(ms))
+
+#define EXPECT_TS_FUZZY(ts, ms) \
+ EXPECT_DOUBLE_EQ(((ts)-MockTimeStamp::Baseline()).ToMilliseconds(), ms)
+
+TEST(TimeConverter, SanityCheck)
+{
+ MockTimeStamp::Init();
+
+ MockCurrentTimeGetter timeGetter(10);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // This call sets the reference time and timestamp
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(10, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance "TimeStamp::Now" by 10ms, use the same event time and OS time.
+ // Since the event time is the same as before, we expect to get back the
+ // same TimeStamp as before too, despite Now() changing.
+ MockTimeStamp::Advance(10);
+ ts = converter.GetTimeStampFromSystemTime(10, unused);
+ EXPECT_TS(ts, 0);
+
+ // Now let's use an event time 20ms after the old event. This will trigger
+ // forward skew detection and resync the TimeStamp for the new event to Now().
+ ts = converter.GetTimeStampFromSystemTime(30, unused);
+ EXPECT_TS(ts, 10);
+}
+
+TEST(TimeConverter, Overflow)
+{
+ // This tests wrapping time around the max value supported in the GTestTime
+ // type and ensuring it is handled properly.
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime almostOverflowed = max - 100;
+ GTestTime overflowed = max + 100;
+ MockCurrentTimeGetter timeGetter(almostOverflowed);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to 100ms before the overflow point
+ TimeStamp ts =
+ converter.GetTimeStampFromSystemTime(almostOverflowed, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance everything by 200ms and verify we get back a TimeStamp 200ms from
+ // the baseline despite wrapping an overflow.
+ MockTimeStamp::Advance(200);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS(ts, 200);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(overflowed, unused);
+ EXPECT_TS_FUZZY(ts, 200.0 + wrapPeriod);
+}
+
+TEST(TimeConverter, InvertedOverflow)
+{
+ // This tests time going from near the min value of GTestTime to the max
+ // value of GTestTime
+
+ MockTimeStamp::Init();
+
+ const GTestTime max = std::numeric_limits<GTestTime>::max();
+ const GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+
+ GTestTime nearRangeMin = min + 100;
+ GTestTime nearRangeMax = max - 100;
+ double gap = (double)nearRangeMax - (double)nearRangeMin;
+
+ MockCurrentTimeGetter timeGetter(nearRangeMin);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+ TimeConverter converter;
+
+ // Set reference time to value near min numeric limit
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(nearRangeMin, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance to value near max numeric limit
+ MockTimeStamp::Advance(gap);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS(ts, gap);
+
+ // Advance by another full wraparound of the time. This loses some precision
+ // so we have to do the FUZZY match
+ MockTimeStamp::Advance(wrapPeriod);
+ ts = converter.GetTimeStampFromSystemTime(nearRangeMax, unused);
+ EXPECT_TS_FUZZY(ts, gap + wrapPeriod);
+}
+
+TEST(TimeConverter, HalfRangeBoundary)
+{
+ MockTimeStamp::Init();
+
+ GTestTime max = std::numeric_limits<GTestTime>::max();
+ GTestTime min = std::numeric_limits<GTestTime>::min();
+ double fullRange = (double)max - (double)min;
+ double wrapPeriod = fullRange + 1.0;
+ GTestTime halfRange = (GTestTime)(fullRange / 2.0);
+ GTestTime halfWrapPeriod = (GTestTime)(wrapPeriod / 2.0);
+
+ TimeConverter converter;
+
+ GTestTime firstEvent = 10;
+ MockCurrentTimeGetter timeGetter(firstEvent);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ // Set reference time
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(firstEvent, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ // Advance event time by just under the half-period, to trigger about as big
+ // a forwards skew as we possibly can.
+ GTestTime secondEvent = firstEvent + (halfWrapPeriod - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+
+ // The above forwards skew will have reset the reference timestamp. Now
+ // advance Now time by just under the half-range, to trigger about as big
+ // a backwards skew as we possibly can.
+ MockTimeStamp::Advance(halfRange - 1);
+ ts = converter.GetTimeStampFromSystemTime(secondEvent, unused);
+ EXPECT_TS(ts, 0);
+}
+
+TEST(TimeConverter, FractionalMillisBug1626734)
+{
+ MockTimeStamp::Init();
+
+ TimeConverter converter;
+
+ GTestTime eventTime = 10;
+ MockCurrentTimeGetter timeGetter(eventTime);
+ UnusedCurrentTimeGetter<GTestTime> unused;
+
+ TimeStamp ts = converter.GetTimeStampFromSystemTime(eventTime, timeGetter);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.2);
+ ts = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts, 0);
+
+ MockTimeStamp::Advance(0.9);
+ TimeStamp ts2 = converter.GetTimeStampFromSystemTime(eventTime, unused);
+ EXPECT_TS(ts2, 0);
+
+ // Since ts2 came from a "future" call relative to ts, we expect ts2 to not
+ // be "before" ts. (i.e. time shouldn't go backwards, even by fractional
+ // milliseconds). This assertion is technically already implied by the
+ // EXPECT_TS checks above, but fixing this assertion is the end result that
+ // we wanted in bug 1626734 so it feels appropriate to recheck it explicitly.
+ EXPECT_TRUE(ts <= ts2);
+}
diff --git a/widget/tests/gtest/TestTouchResampler.cpp b/widget/tests/gtest/TestTouchResampler.cpp
new file mode 100644
index 0000000000..1a5b8e2430
--- /dev/null
+++ b/widget/tests/gtest/TestTouchResampler.cpp
@@ -0,0 +1,941 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <initializer_list>
+#include "InputData.h"
+#include "Units.h"
+#include "gtest/gtest.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TimeStamp.h"
+#include "TouchResampler.h"
+
+using namespace mozilla;
+using widget::TouchResampler;
+
+class TouchResamplerTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); }
+
+ TimeStamp Time(double aMilliseconds) {
+ return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds);
+ }
+
+ uint64_t ProcessEvent(
+ MultiTouchInput::MultiTouchType aType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aHistoricalData,
+ const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) {
+ MultiTouchInput input(aType, 0, aTimeStamp, 0);
+ input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f));
+ for (const auto& histData : aHistoricalData) {
+ input.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ histData.first, histData.second, {}, {}, 0.0f, 0.0f});
+ }
+ return resampler.ProcessEvent(std::move(input));
+ }
+
+ void CheckTime(const TimeStamp& aTimeStamp,
+ const TimeStamp& aExpectedTimeStamp) {
+ EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(),
+ (aExpectedTimeStamp - baseTimeStamp).ToMilliseconds());
+ }
+
+ void CheckEvent(const MultiTouchInput& aEvent,
+ MultiTouchInput::MultiTouchType aExpectedType,
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
+ aExpectedHistoricalData,
+ const TimeStamp& aExpectedTimeStamp,
+ const ScreenIntPoint& aExpectedPosition) {
+ EXPECT_EQ(aEvent.mType, aExpectedType);
+ EXPECT_EQ(aEvent.mTouches.Length(), size_t(1));
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(),
+ aExpectedHistoricalData.size());
+ for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) {
+ CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp,
+ aExpectedHistoricalData.begin()[i].first);
+ EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint,
+ aExpectedHistoricalData.begin()[i].second);
+ }
+ CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp);
+ EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition);
+ }
+
+ struct ExpectedOutgoingEvent {
+ Maybe<uint64_t> mEventId;
+ MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START;
+ std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> mHistoricalData;
+ TimeStamp mTimeStamp;
+ ScreenIntPoint mPosition;
+ };
+
+ void CheckOutgoingEvents(
+ std::initializer_list<ExpectedOutgoingEvent> aExpectedEvents) {
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), aExpectedEvents.size());
+ for (const auto& expectedEvent : aExpectedEvents) {
+ auto outgoingEvent = std::move(outgoing.front());
+ outgoing.pop();
+
+ EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId);
+ CheckEvent(outgoingEvent.mEvent, expectedEvent.mType,
+ expectedEvent.mHistoricalData, expectedEvent.mTimeStamp,
+ expectedEvent.mPosition);
+ }
+ }
+
+ TimeStamp baseTimeStamp;
+ TouchResampler resampler;
+};
+
+TEST_F(TouchResamplerTest, BasicExtrapolation) {
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20---------------32------------
+ // * touchstart at (10, 10)
+ // * touchmove at (26, 26)
+ // * touchmove at (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an extrapolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(16.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(16.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(32.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, BasicInterpolation) {
+ // Same test as BasicExtrapolation, but with a frame time that's 10ms earlier.
+ //
+ // Execute the following sequence:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove at (20, 20)
+ // * frame
+ // * touchend at (20, 20)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0------6---10-----------20--22------------30-------------
+ // * touchstart at (10, 10)
+ // * touchmove (16, 16)
+ // * touchmove (20, 20)
+ // * touchend at (20, 20)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchstart and touchmove events.
+ // The touchend should create a touchmove that returns back to a non-resampled
+ // position.
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ EXPECT_TRUE(resampler.InTouchingState());
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+
+ resampler.NotifyFrame(Time(6.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(6.0),
+ ScreenIntPoint(16, 16)},
+ });
+
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
+ ScreenIntPoint(20, 20));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ CheckOutgoingEvents({
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(10.0),
+ ScreenIntPoint(20, 20)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(20.0),
+ ScreenIntPoint(20, 20)},
+ });
+
+ // No more events should be produced from here on out.
+ resampler.NotifyFrame(Time(22.0));
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_TRUE(outgoing.empty());
+}
+
+TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) {
+ // Interpolate from the historical data in a touch move event.
+ //
+ // Execute the following sequence:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `---------------* touchmove at (30, 30)
+ // * frame
+ // * touchend at (30, 30)
+ // * frame
+ //
+ // And expect the following output:
+ //
+ // 0----------10-------16-----20-----------30--32------------
+ // * touchstart at (10, 10)
+ // * [hist] at (20, 25) for
+ // `--------* touchmove at (26, 28)
+ // * touchmove at (30, 30)
+ // * touchend at (30, 30)
+ //
+ // The first frame should emit an interpolated touchmove from the position
+ // data in the touchmove event, and integrate the historical data point into
+ // the resampled event.
+ // The touchend should force a synthesized touchmove that returns back to a
+ // non-resampled position.
+
+ // This also tests that interpolation works for both x and y, by giving the
+ // historical datapoint different values for x and y.
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(20.0), ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(16.0));
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0),
+ ScreenIntPoint(30, 30));
+ resampler.NotifyFrame(Time(32.0));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}},
+ Time(16.0),
+ ScreenIntPoint(26, 28)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(20.0),
+ ScreenIntPoint(30, 30)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(30.0),
+ ScreenIntPoint(30, 30)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleTouches) {
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0),
+ 0);
+ inputStart0.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f));
+ auto idStart0 = resampler.ProcessEvent(std::move(inputStart0));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0),
+ 0);
+ inputMove1.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f));
+ inputMove1.mTouches[0].mHistoricalData.AppendElement(
+ SingleTouchData::HistoricalTouchData{
+ Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f});
+ auto idMove1 = resampler.ProcessEvent(std::move(inputMove1));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(16.0));
+
+ // Touch move
+ MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0),
+ 0);
+ inputMove2.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idMove2 = resampler.ProcessEvent(std::move(inputMove2));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch start
+ MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0),
+ 0);
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f));
+ inputStart3.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
+ auto idStart3 = resampler.ProcessEvent(std::move(inputStart3));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch move
+ MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0),
+ 0);
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f));
+ inputMove4.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f));
+ auto idMove4 = resampler.ProcessEvent(std::move(inputMove4));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(32.0));
+
+ // Touch move
+ MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0),
+ 0);
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ inputMove5.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f));
+ auto idMove5 = resampler.ProcessEvent(std::move(inputMove5));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Touch end
+ MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0);
+ // Touch point with identifier 1 is lifted
+ inputEnd6.mTouches.AppendElement(
+ SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
+ auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(48.0));
+
+ // Touch move
+ MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0),
+ 0);
+ inputMove7.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idMove7 = resampler.ProcessEvent(std::move(inputMove7));
+ EXPECT_TRUE(resampler.InTouchingState());
+
+ // Frame
+ resampler.NotifyFrame(Time(64.0));
+
+ // Touch end
+ MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0);
+ // Touch point with identifier 2 is lifted
+ inputEnd8.mTouches.AppendElement(
+ SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
+ auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8));
+ EXPECT_FALSE(resampler.InTouchingState());
+
+ // Check outgoing events
+ auto outgoing = resampler.ConsumeOutgoingEvents();
+ EXPECT_EQ(outgoing.size(), size_t(9));
+
+ auto outgoingStart0 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0));
+ CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {},
+ Time(0.0), ScreenIntPoint(10, 10));
+
+ auto outgoingMove1 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1));
+ // (26, 28) is 60% of the way from (20, 25) to (30, 30).
+ CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0),
+ ScreenIntPoint(26, 28));
+
+ auto outgoingMove2 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2));
+ CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0),
+ ScreenIntPoint(30, 40));
+
+ auto outgoingStart3 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3));
+ EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START);
+ CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch start event
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(100, 10));
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1);
+ EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(30, 40));
+
+ auto outgoingMove4 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4));
+ EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2));
+ // Touch order should be taken from the original touch move event.
+ // Both touches should be resampled.
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 42));
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 14));
+
+ auto outgoingMove5 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5));
+ EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
+ CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2));
+ // touch order should be taken from the original touch move event
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint,
+ ScreenIntPoint(30, 60));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(30, 50));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2);
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint,
+ ScreenIntPoint(100, 40));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(),
+ size_t(1));
+ CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp,
+ Time(40.0));
+ EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint,
+ ScreenIntPoint(100, 30));
+
+ auto outgoingEnd6 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6));
+ CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(50.0), ScreenIntPoint(30, 60));
+
+ auto outgoingMove7 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7));
+ // No extrapolation because the frame at 64.0 cleared the data points because
+ // there was no pending touch move event at that point
+ CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {},
+ Time(60.0), ScreenIntPoint(100, 60));
+ EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2);
+
+ auto outgoingEnd8 = std::move(outgoing.front());
+ outgoing.pop();
+ EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8));
+ CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {},
+ Time(70.0), ScreenIntPoint(100, 60));
+}
+
+TEST_F(TouchResamplerTest, MovingPauses) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(10, 10));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(20, 20));
+ resampler.NotifyFrame(Time(16.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0),
+ ScreenIntPoint(40, 40));
+ resampler.NotifyFrame(Time(32.0));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(50, 40));
+ resampler.NotifyFrame(Time(48.0));
+ resampler.NotifyFrame(Time(64.0));
+ auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(50, 40));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(10, 10)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(20, 20)}},
+ Time(16.0),
+ ScreenIntPoint(26, 26)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(40, 40)}},
+ Time(32.0),
+ ScreenIntPoint(42, 42)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(40.0), ScreenIntPoint(50, 40)}},
+ Time(48.0),
+ ScreenIntPoint(58, 40)},
+
+ // There was no event between two frames here, so we expect a reset event,
+ // so that we pause at a non-resampled position.
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(48.0),
+ ScreenIntPoint(50, 40)},
+
+ {Some(idEnd4),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(50, 40)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ resampler.NotifyFrame(Time(27.0)); // 32 - 5
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(27.0),
+ ScreenIntPoint(0, 27)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(30.0), ScreenIntPoint(0, 30)},
+ {Time(40.0), ScreenIntPoint(0, 40)}},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(50.0), ScreenIntPoint(0, 50)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, MultipleMoveEvents) {
+ // Test what happens if multiple touch move events appear between two frames.
+ // This scenario shouldn't occur on Android but we should be able to deal with
+ // it anyway. Check that we don't discard any event IDs.
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
+ ScreenIntPoint(0, 10));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
+ ScreenIntPoint(0, 30));
+ auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
+ ScreenIntPoint(0, 40));
+ auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0),
+ ScreenIntPoint(0, 50));
+ auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(43.0)); // 48 - 5
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(11.0),
+ ScreenIntPoint(0, 11)},
+
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(30.0),
+ ScreenIntPoint(0, 30)},
+
+ {Some(idMove3),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idMove4),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(43.0),
+ ScreenIntPoint(0, 43)},
+
+ {Some(idMove5),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(45.0), ScreenIntPoint(0, 45)},
+ {Time(50.0), ScreenIntPoint(0, 50)},
+ {Time(55.0), ScreenIntPoint(0, 55)}},
+ Time(59.0),
+ ScreenIntPoint(0, 59)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(59.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd5),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitFuturePrediction) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxPredictMs == 8
+ // Refuse to predict more than 8ms into the future, the fingers might have
+ // paused. Make an event for time 52 (= 44 + 8) instead of 59.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)},
+ {Time(44.0), ScreenIntPoint(0, 44)}},
+ Time(52.0),
+ ScreenIntPoint(0, 52)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(52.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, LimitBacksampling) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 44, then pause. UI thread is occupied until 64.
+ // Then we get a frame callback with a very outdated frametime.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0), ScreenIntPoint(0, 44));
+ resampler.NotifyFrame(Time(11.0)); // 16 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleMaxBacksampleMs == 20
+ // Refuse to sample further back than 20ms before the last data point.
+ // Make an event for time 24 (= 44 - 20) instead of time 11.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)}},
+ Time(24.0),
+ ScreenIntPoint(0, 24)},
+
+ {Nothing(),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(32.0), ScreenIntPoint(0, 32)}},
+ Time(44.0),
+ ScreenIntPoint(0, 44)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 40, then pause. UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0), ScreenIntPoint(0, 40));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 44));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleOldTouchThresholdMs == 17
+ // Refuse to extrapolate from a data point that's more than 17ms older
+ // than the frame time.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(20.0), ScreenIntPoint(0, 20)},
+ {Time(30.0), ScreenIntPoint(0, 30)}},
+ Time(40.0),
+ ScreenIntPoint(0, 40)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 44)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 55.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0),
+ ScreenIntPoint(0, 55));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(55.0),
+ ScreenIntPoint(0, 55)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ // Fingers move until time 10, pause, and move again at 60.
+ // UI thread is occupied until 64.
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0),
+ ScreenIntPoint(0, 60));
+ resampler.NotifyFrame(Time(59.0)); // 64 - 5
+ auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
+ ScreenIntPoint(0, 60));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ // kTouchResampleWindowSize == 40
+ // Refuse to resample between two data points that are more than 40ms
+ // apart.
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}},
+ Time(60.0),
+ ScreenIntPoint(0, 60)},
+
+ {Some(idEnd2),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(70.0),
+ ScreenIntPoint(0, 60)},
+ });
+}
+
+TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) {
+ auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
+ ScreenIntPoint(0, 0));
+ auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0),
+ ScreenIntPoint(0, 16));
+ resampler.NotifyFrame(Time(20.0));
+ auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0),
+ ScreenIntPoint(0, 25));
+ auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0),
+ ScreenIntPoint(0, 25));
+
+ CheckOutgoingEvents({
+ {Some(idStart0),
+ MultiTouchInput::MULTITOUCH_START,
+ {},
+ Time(0.0),
+ ScreenIntPoint(0, 0)},
+
+ {Some(idMove1),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {{Time(10.0), ScreenIntPoint(0, 10)},
+ {Time(16.0), ScreenIntPoint(0, 16)}},
+ Time(20.0),
+ ScreenIntPoint(0, 20)},
+
+ // Discard the historical data point from time 18, because we've already
+ // sent out an event with time 20 and don't want to go back before that.
+ {Some(idMove2),
+ MultiTouchInput::MULTITOUCH_MOVE,
+ {},
+ Time(25.0),
+ ScreenIntPoint(0, 25)},
+
+ {Some(idEnd3),
+ MultiTouchInput::MULTITOUCH_END,
+ {},
+ Time(35.0),
+ ScreenIntPoint(0, 25)},
+ });
+}
diff --git a/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp b/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp
new file mode 100644
index 0000000000..5ac361fea0
--- /dev/null
+++ b/widget/tests/gtest/TestWinHeaderOnlyUtils.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include <shlwapi.h>
+
+TEST(WinHeaderOnlyUtils, MozPathGetDriveNumber)
+{
+ constexpr auto TestPathGetDriveNumber = [](const wchar_t* aPath) {
+ return mozilla::MozPathGetDriveNumber(aPath) ==
+ ::PathGetDriveNumberW(aPath);
+ };
+ EXPECT_TRUE(TestPathGetDriveNumber(nullptr));
+ EXPECT_TRUE(TestPathGetDriveNumber(L""));
+ EXPECT_TRUE(TestPathGetDriveNumber(L" :"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"a:"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"C:\\file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"x:/file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"@:\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"B"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"abc:\\file"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\:A"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\c:\\"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\A"));
+ EXPECT_TRUE(TestPathGetDriveNumber(L"\\\\?\\ \\"));
+}
diff --git a/widget/tests/gtest/TestWinMessageLoggingUtils.cpp b/widget/tests/gtest/TestWinMessageLoggingUtils.cpp
new file mode 100644
index 0000000000..b0ab09f866
--- /dev/null
+++ b/widget/tests/gtest/TestWinMessageLoggingUtils.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "windows/nsWindowDbg.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_CombinationFlagsHandledFirst)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x3, "COMBO"}, {0x1, "ONE"}, {0x2, "TWO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x3, flags, nullptr));
+ EXPECT_STREQ("COMBO", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_SingleFlag)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x4, flags, nullptr));
+ EXPECT_STREQ("FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_SingleFlagWithName)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x4, flags, "paramName"));
+ EXPECT_STREQ("paramName=FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_MultipleFlags)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x5, flags, nullptr));
+ EXPECT_STREQ("ONE|FOUR", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NoFlags)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, nullptr));
+ EXPECT_STREQ("Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_OnlySomeFlagsMatch)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x9, flags, nullptr));
+ EXPECT_STREQ("ONE|Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_FlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x2, flags, nullptr));
+ EXPECT_STREQ("TWO", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NoFlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, nullptr));
+ EXPECT_STREQ("Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_NameAndNoFlagsMatch_NoZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x8, flags, "paramName"));
+ EXPECT_STREQ("paramName=Unknown (0x00000008)", result.get());
+}
+
+TEST(WinMessageLoggingUtils, AppendFlagsInfo_ValueIsZero_ZeroReturned)
+{
+ const nsTArray<EnumValueAndName> flags = {
+ {0x1, "ONE"}, {0x2, "TWO"}, {0x4, "FOUR"}, {0x0, "ZERO"}};
+ nsAutoCString result;
+ EXPECT_EQ(true, AppendFlagsInfo(result, 0x0, flags, nullptr));
+ EXPECT_STREQ("ZERO", result.get());
+}
diff --git a/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp b/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp
new file mode 100644
index 0000000000..b1f1114a3a
--- /dev/null
+++ b/widget/tests/gtest/TestWinWindowOcclusionTracker.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include <dwmapi.h>
+#include <windows.h>
+
+#include "MockWinWidget.h"
+#include "mozilla/widget/WinWindowOcclusionTracker.h"
+#include "mozilla/WindowsVersion.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+class WinWindowOcclusionTrackerTest : public ::testing::Test {
+ protected:
+ HWND CreateNativeWindow(DWORD aStyle, DWORD aExStyle) {
+ mMockWinWidget =
+ MockWinWidget::Create(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | aStyle,
+ aExStyle, LayoutDeviceIntRect(0, 0, 100, 100));
+ EXPECT_NE(nullptr, mMockWinWidget.get());
+ HWND hwnd = mMockWinWidget->GetWnd();
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ EXPECT_NE(nullptr, region);
+ if (::GetWindowRgn(hwnd, region) == COMPLEXREGION) {
+ // On Windows 7, the newly created window has a complex region, which
+ // means it will be ignored during the occlusion calculation. So, force
+ // it to have a simple region so that we get test coverage on win 7.
+ RECT boundingRect;
+ EXPECT_TRUE(::GetWindowRect(hwnd, &boundingRect));
+ HRGN rectangularRegion = ::CreateRectRgnIndirect(&boundingRect);
+ EXPECT_NE(nullptr, rectangularRegion);
+ ::SetWindowRgn(hwnd, rectangularRegion, /* bRedraw = */ TRUE);
+ ::DeleteObject(rectangularRegion);
+ }
+ ::DeleteObject(region);
+
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ EXPECT_TRUE(UpdateWindow(hwnd));
+ return hwnd;
+ }
+
+ // Wrapper around IsWindowVisibleAndFullyOpaque so only the test class
+ // needs to be a friend of NativeWindowOcclusionTrackerWin.
+ bool CheckWindowVisibleAndFullyOpaque(HWND aHWnd,
+ LayoutDeviceIntRect* aWinRect) {
+ bool ret = WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
+ aHWnd, aWinRect);
+ // In general, if IsWindowVisibleAndFullyOpaque returns false, the
+ // returned rect should not be altered.
+ if (!ret) {
+ EXPECT_EQ(*aWinRect, LayoutDeviceIntRect(0, 0, 0, 0));
+ }
+ return ret;
+ }
+
+ RefPtr<MockWinWidget> mMockWinWidget;
+};
+
+TEST_F(WinWindowOcclusionTrackerTest, VisibleOpaqueWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect returnedRect;
+ // Normal windows should be visible.
+ EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &returnedRect));
+
+ // Check that the returned rect == the actual window rect of the hwnd.
+ RECT winRect;
+ ASSERT_TRUE(::GetWindowRect(hwnd, &winRect));
+ EXPECT_EQ(returnedRect, LayoutDeviceIntRect(winRect.left, winRect.top,
+ winRect.right - winRect.left,
+ winRect.bottom - winRect.top));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, MinimizedWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ ::ShowWindow(hwnd, SW_MINIMIZE);
+ // Minimized windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, TransparentWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_TRANSPARENT);
+ LayoutDeviceIntRect winRect;
+ // Transparent windows are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, ToolWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_TOOLWINDOW);
+ LayoutDeviceIntRect winRect;
+ // Tool windows are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, LayeredAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ BYTE alpha = 1;
+ DWORD flags = LWA_ALPHA;
+ COLORREF colorRef = RGB(1, 1, 1);
+ SetLayeredWindowAttributes(hwnd, colorRef, alpha, flags);
+ // Layered windows with alpha < 255 are not considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, UpdatedLayeredAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ HDC hdc = ::CreateCompatibleDC(nullptr);
+ EXPECT_NE(nullptr, hdc);
+ BLENDFUNCTION blend = {AC_SRC_OVER, 0x00, 0xFF, AC_SRC_ALPHA};
+
+ ::UpdateLayeredWindow(hwnd, hdc, nullptr, nullptr, nullptr, nullptr,
+ RGB(0xFF, 0xFF, 0xFF), &blend, ULW_OPAQUE);
+ // Layered windows set up with UpdateLayeredWindow instead of
+ // SetLayeredWindowAttributes should not be considered visible and opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+ ::DeleteDC(hdc);
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, LayeredNonAlphaWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, WS_EX_LAYERED);
+ LayoutDeviceIntRect winRect;
+ BYTE alpha = 1;
+ DWORD flags = 0;
+ COLORREF colorRef = RGB(1, 1, 1);
+ ::SetLayeredWindowAttributes(hwnd, colorRef, alpha, flags);
+ // Layered non alpha windows are considered visible and opaque.
+ EXPECT_TRUE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, ComplexRegionWindow) {
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ // Create a region with rounded corners, which should be a complex region.
+ HRGN region = CreateRoundRectRgn(1, 1, 100, 100, 5, 5);
+ EXPECT_NE(nullptr, region);
+ ::SetWindowRgn(hwnd, region, /* bRedraw = */ TRUE);
+ // Windows with complex regions are not considered visible and fully opaque.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+ DeleteObject(region);
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, PopupWindow) {
+ HWND hwnd = CreateNativeWindow(WS_POPUP, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ // Normal Popup Windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
+
+TEST_F(WinWindowOcclusionTrackerTest, CloakedWindow) {
+ // Cloaking is only supported in Windows 8 and above.
+ if (!IsWin8OrLater()) {
+ return;
+ }
+ HWND hwnd = CreateNativeWindow(/* aStyle = */ 0, /* aExStyle = */ 0);
+ LayoutDeviceIntRect winRect;
+ BOOL cloak = TRUE;
+ ::DwmSetWindowAttribute(hwnd, DWMWA_CLOAK, &cloak, sizeof(cloak));
+ // Cloaked Windows are not considered visible.
+ EXPECT_FALSE(CheckWindowVisibleAndFullyOpaque(hwnd, &winRect));
+}
diff --git a/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
new file mode 100644
index 0000000000..13cd95651c
--- /dev/null
+++ b/widget/tests/gtest/TestWinWindowOcclusionTrackerInteractive.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "MockWinWidget.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/widget/WinEventObserver.h"
+#include "mozilla/widget/WinWindowOcclusionTracker.h"
+#include "nsThreadUtils.h"
+#include "Units.h"
+#include "WinUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#define PREF_DISPLAY_STATE \
+ "widget.windows.window_occlusion_tracking_display_state.enabled"
+#define PREF_SESSION_LOCK \
+ "widget.windows.window_occlusion_tracking_session_lock.enabled"
+
+class WinWindowOcclusionTrackerInteractiveTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Shut down WinWindowOcclusionTracker if it exists.
+ // This could happen when WinWindowOcclusionTracker was initialized by other
+ // gtest
+ if (WinWindowOcclusionTracker::Get()) {
+ WinWindowOcclusionTracker::ShutDown();
+ }
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Ensure();
+ EXPECT_NE(nullptr, WinWindowOcclusionTracker::Get());
+
+ WinWindowOcclusionTracker::Get()->EnsureDisplayStatusObserver();
+ WinWindowOcclusionTracker::Get()->EnsureSessionChangeObserver();
+ }
+
+ void TearDown() override {
+ WinWindowOcclusionTracker::ShutDown();
+ EXPECT_EQ(nullptr, WinWindowOcclusionTracker::Get());
+ }
+
+ void SetNativeWindowBounds(HWND aHWnd, const LayoutDeviceIntRect aBounds) {
+ RECT wr;
+ wr.left = aBounds.X();
+ wr.top = aBounds.Y();
+ wr.right = aBounds.XMost();
+ wr.bottom = aBounds.YMost();
+
+ ::AdjustWindowRectEx(&wr, ::GetWindowLong(aHWnd, GWL_STYLE), FALSE,
+ ::GetWindowLong(aHWnd, GWL_EXSTYLE));
+
+ // Make sure to keep the window onscreen, as AdjustWindowRectEx() may have
+ // moved part of it offscreen. But, if the original requested bounds are
+ // offscreen, don't adjust the position.
+ LayoutDeviceIntRect windowBounds(wr.left, wr.top, wr.right - wr.left,
+ wr.bottom - wr.top);
+
+ if (aBounds.X() >= 0) {
+ windowBounds.x = std::max(0, windowBounds.X());
+ }
+ if (aBounds.Y() >= 0) {
+ windowBounds.y = std::max(0, windowBounds.Y());
+ }
+ ::SetWindowPos(aHWnd, HWND_TOP, windowBounds.X(), windowBounds.Y(),
+ windowBounds.Width(), windowBounds.Height(),
+ SWP_NOREPOSITION);
+ EXPECT_TRUE(::UpdateWindow(aHWnd));
+ }
+
+ void CreateNativeWindowWithBounds(LayoutDeviceIntRect aBounds) {
+ mMockWinWidget = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle = */ 0, aBounds);
+ EXPECT_NE(nullptr, mMockWinWidget.get());
+ HWND hwnd = mMockWinWidget->GetWnd();
+ SetNativeWindowBounds(hwnd, aBounds);
+ HRGN region = ::CreateRectRgn(0, 0, 0, 0);
+ EXPECT_NE(nullptr, region);
+ if (::GetWindowRgn(hwnd, region) == COMPLEXREGION) {
+ // On Windows 7, the newly created window has a complex region, which
+ // means it will be ignored during the occlusion calculation. So, force
+ // it to have a simple region so that we get test coverage on win 7.
+ RECT boundingRect;
+ EXPECT_TRUE(::GetWindowRect(hwnd, &boundingRect));
+ HRGN rectangularRegion = ::CreateRectRgnIndirect(&boundingRect);
+ EXPECT_NE(nullptr, rectangularRegion);
+ ::SetWindowRgn(hwnd, rectangularRegion, /* bRedraw = */ TRUE);
+ ::DeleteObject(rectangularRegion);
+ }
+ ::DeleteObject(region);
+
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ EXPECT_TRUE(UpdateWindow(hwnd));
+ }
+
+ RefPtr<MockWinWidget> CreateTrackedWindowWithBounds(
+ LayoutDeviceIntRect aBounds) {
+ RefPtr<MockWinWidget> window = MockWinWidget::Create(
+ WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, /* aExStyle */ 0, aBounds);
+ EXPECT_NE(nullptr, window.get());
+ HWND hwnd = window->GetWnd();
+ ::ShowWindow(hwnd, SW_SHOWNORMAL);
+ WinWindowOcclusionTracker::Get()->Enable(window, window->GetWnd());
+ return window;
+ }
+
+ int GetNumVisibleRootWindows() {
+ return WinWindowOcclusionTracker::Get()->mNumVisibleRootWindows;
+ }
+
+ void OnDisplayStateChanged(bool aDisplayOn) {
+ WinWindowOcclusionTracker::Get()->OnDisplayStateChanged(aDisplayOn);
+ }
+
+ RefPtr<MockWinWidget> mMockWinWidget;
+};
+
+// Simple test completely covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test partially covering a tracked window with a native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, PartialOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test that a partly off screen tracked window, with the on screen part
+// occluded by a native window, is considered occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, OffscreenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Move the tracked window 50 pixels offscreen to the left.
+ int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ ::SetWindowPos(window->GetWnd(), HWND_TOP, screenLeft - 50, 0, 100, 100,
+ SWP_NOZORDER | SWP_NOSIZE);
+
+ // Create a native window that covers the onscreen part of the tracked window.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(screenLeft, 0, 50, 100));
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a tracked window and native window that do not overlap.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleVisible) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Simple test with a minimized tracked window and native window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, SimpleHidden) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(200, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that minimizing and restoring a tracked window results in the occlusion
+// tracker re-registering for win events and detecting that a native window
+// occludes the tracked window.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest,
+ OcclusionAfterVisibilityToggle) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ false);
+
+ // This makes the window iconic.
+ ::CloseWindow(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // HIDDEN state is set synchronously by OnWindowVsiblityChanged notification,
+ // before occlusion is calculated, so the above expectation will be met w/o an
+ // occlusion calculation.
+ // Loop until an occlusion calculation has run with no non-hidden windows.
+ while (GetNumVisibleRootWindows() != 0) {
+ // Need to pump events in order for UpdateOcclusionState to get called, and
+ // update the number of non hidden windows. When that number is 0,
+ // occlusion has been calculated with no visible tracked windows.
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ WinWindowOcclusionTracker::Get()->OnWindowVisibilityChanged(
+ window, /* aVisible = */ true);
+ // This opens the window made iconic above.
+ ::OpenIcon(window->GetWnd());
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Open a native window that occludes the visible tracked window.
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen causes visible windows to become occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenVisibleOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen leaves hidden windows as hidden.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenHiddenOcclusion) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ // Iconify the tracked window and check that its occlusion state is HIDDEN.
+ ::CloseWindow(window->GetWnd());
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to VISIBLE.
+ window->NotifyOcclusionState(widget::OcclusionState::VISIBLE);
+
+ window->SetExpectation(widget::OcclusionState::HIDDEN);
+
+ // Unfortunately, this relies on knowing that NativeWindowOcclusionTracker
+ // uses SessionChangeObserver to listen for WM_WTSSESSION_CHANGE messages, but
+ // actually locking the screen isn't feasible.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId);
+
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that locking the screen from a different session doesn't mark window
+// as occluded.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, LockScreenDifferentSession) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 200, 200));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ // Force the state to OCCLUDED.
+ window->NotifyOcclusionState(widget::OcclusionState::OCCLUDED);
+
+ // Generate a session change lock screen with a session id that's not
+ // currentSessionId.
+ DWORD currentSessionId = 0;
+ ::ProcessIdToSessionId(::GetCurrentProcessId(), &currentSessionId);
+ ::PostMessage(WinEventHub::Get()->GetWnd(), WM_WTSSESSION_CHANGE,
+ WTS_SESSION_LOCK, currentSessionId + 1);
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ // Create a native window to trigger occlusion calculation.
+ CreateNativeWindowWithBounds(LayoutDeviceIntRect(0, 0, 50, 50));
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ MSG msg;
+ bool gotMessage =
+ ::PeekMessageW(&msg, WinEventHub::Get()->GetWnd(), 0, 0, PM_REMOVE);
+ if (gotMessage) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ false);
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
+
+// Test that display off & on power state notification causes visible windows to
+// become occluded, then visible.
+TEST_F(WinWindowOcclusionTrackerInteractiveTest, DisplayOnOffHandling) {
+ RefPtr<MockWinWidget> window =
+ CreateTrackedWindowWithBounds(LayoutDeviceIntRect(0, 0, 100, 100));
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::OCCLUDED);
+
+ // Turning display off and on isn't feasible, so send a notification.
+ OnDisplayStateChanged(/* aDisplayOn */ false);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+
+ window->SetExpectation(widget::OcclusionState::VISIBLE);
+ OnDisplayStateChanged(/* aDisplayOn */ true);
+ while (window->IsExpectingCall()) {
+ WinWindowOcclusionTracker::Get()->TriggerCalculation();
+
+ NS_ProcessNextEvent(nullptr, /* aMayWait = */ true);
+ }
+ EXPECT_FALSE(window->IsExpectingCall());
+}
diff --git a/widget/tests/gtest/moz.build b/widget/tests/gtest/moz.build
new file mode 100644
index 0000000000..613844fa78
--- /dev/null
+++ b/widget/tests/gtest/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES = [
+ "TestTimeConverter.cpp",
+ "TestTouchResampler.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "MockWinWidget.cpp",
+ "TestWinHeaderOnlyUtils.cpp",
+ "TestWinMessageLoggingUtils.cpp",
+ "TestWinWindowOcclusionTracker.cpp",
+ "TestWinWindowOcclusionTrackerInteractive.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "/widget",
+]
+
+DisableStlWrapping()