summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp
blob: cd7e0a7d0d7981178b84f8ed487c63197a5e46b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* -*- 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 "gmock/gmock.h"

#include <functional>

#include "MobileViewportManager.h"
#include "mozilla/MVMContext.h"
#include "mozilla/dom/Event.h"

using namespace mozilla;

class MockMVMContext : public MVMContext {
  using AutoSizeFlag = nsViewportInfo::AutoSizeFlag;
  using AutoScaleFlag = nsViewportInfo::AutoScaleFlag;
  using ZoomFlag = nsViewportInfo::ZoomFlag;

  // A "layout function" is a function that computes the content size
  // as a function of the ICB size.
  using LayoutFunction = std::function<CSSSize(CSSSize aICBSize)>;

 public:
  // MVMContext methods we don't care to implement.
  MOCK_METHOD3(AddEventListener,
               void(const nsAString& aType, nsIDOMEventListener* aListener,
                    bool aUseCapture));
  MOCK_METHOD3(RemoveEventListener,
               void(const nsAString& aType, nsIDOMEventListener* aListener,
                    bool aUseCapture));
  MOCK_METHOD3(AddObserver, void(nsIObserver* aObserver, const char* aTopic,
                                 bool aOwnsWeak));
  MOCK_METHOD2(RemoveObserver,
               void(nsIObserver* aObserver, const char* aTopic));
  MOCK_METHOD0(Destroy, void());

  MOCK_METHOD1(SetVisualViewportSize, void(const CSSSize& aSize));
  MOCK_METHOD0(PostVisualViewportResizeEventByDynamicToolbar, void());
  MOCK_METHOD0(UpdateDisplayPortMargins, void());

  void SetMVM(MobileViewportManager* aMVM) { mMVM = aMVM; }

  // MVMContext method implementations.
  nsViewportInfo GetViewportInfo(const ScreenIntSize& aDisplaySize) const {
    // This is a very basic approximation of what Document::GetViewportInfo()
    // does in the most common cases.
    // Ideally, we would invoke the algorithm in Document::GetViewportInfo()
    // itself, but that would require refactoring it a bit to remove
    // dependencies on the actual Document which we don't have available in
    // this test harness.
    CSSSize viewportSize = mDisplaySize / mDeviceScale;
    if (mAutoSizeFlag == AutoSizeFlag::FixedSize) {
      viewportSize = CSSSize(mFixedViewportWidth,
                             mFixedViewportWidth * (float(mDisplaySize.height) /
                                                    mDisplaySize.width));
    }
    return nsViewportInfo(mDefaultScale, mMinScale, mMaxScale, viewportSize,
                          mAutoSizeFlag, mAutoScaleFlag, mZoomFlag,
                          dom::ViewportFitType::Auto);
  }
  CSSToLayoutDeviceScale CSSToDevPixelScale() const { return mDeviceScale; }
  float GetResolution() const { return mResolution; }
  bool SubjectMatchesDocument(nsISupports* aSubject) const { return true; }
  Maybe<CSSRect> CalculateScrollableRectForRSF() const {
    return Some(CSSRect(CSSPoint(), mContentSize));
  }
  bool IsResolutionUpdatedByApz() const { return false; }
  LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() const {
    return LayoutDeviceMargin();
  }
  Maybe<LayoutDeviceIntSize> GetContentViewerSize() const {
    return Some(mDisplaySize);
  }
  bool AllowZoomingForDocument() const { return true; }
  bool IsInReaderMode() const { return false; }
  bool IsDocumentLoading() const { return false; }

  void SetResolutionAndScaleTo(float aResolution,
                               ResolutionChangeOrigin aOrigin) {
    mResolution = aResolution;
    mMVM->ResolutionUpdated(aOrigin);
  }
  void Reflow(const CSSSize& aNewSize) {
    mICBSize = aNewSize;
    mContentSize = mLayoutFunction(mICBSize);
  }

  // Allow test code to modify the input metrics.
  void SetMinScale(CSSToScreenScale aMinScale) { mMinScale = aMinScale; }
  void SetMaxScale(CSSToScreenScale aMaxScale) { mMaxScale = aMaxScale; }
  void SetInitialScale(CSSToScreenScale aInitialScale) {
    mDefaultScale = aInitialScale;
    mAutoScaleFlag = AutoScaleFlag::FixedScale;
  }
  void SetFixedViewportWidth(CSSCoord aWidth) {
    mFixedViewportWidth = aWidth;
    mAutoSizeFlag = AutoSizeFlag::FixedSize;
  }
  void SetDisplaySize(const LayoutDeviceIntSize& aNewDisplaySize) {
    mDisplaySize = aNewDisplaySize;
  }
  void SetLayoutFunction(const LayoutFunction& aLayoutFunction) {
    mLayoutFunction = aLayoutFunction;
  }

  // Allow test code to query the output metrics.
  CSSSize GetICBSize() const { return mICBSize; }
  CSSSize GetContentSize() const { return mContentSize; }

 private:
  // Input metrics, with some sensible defaults.
  LayoutDeviceIntSize mDisplaySize{300, 600};
  CSSToScreenScale mDefaultScale{1.0f};
  CSSToScreenScale mMinScale{0.25f};
  CSSToScreenScale mMaxScale{10.0f};
  CSSToLayoutDeviceScale mDeviceScale{1.0f};
  CSSCoord mFixedViewportWidth;
  AutoSizeFlag mAutoSizeFlag = AutoSizeFlag::AutoSize;
  AutoScaleFlag mAutoScaleFlag = AutoScaleFlag::AutoScale;
  ZoomFlag mZoomFlag = ZoomFlag::AllowZoom;
  // As a default layout function, just set the content size to the ICB size.
  LayoutFunction mLayoutFunction = [](CSSSize aICBSize) { return aICBSize; };

  // Output metrics.
  float mResolution = 1.0f;
  CSSSize mICBSize;
  CSSSize mContentSize;

  MobileViewportManager* mMVM = nullptr;
};

class MVMTester : public ::testing::Test {
 public:
  MVMTester()
      : mMVMContext(new MockMVMContext()),
        mMVM(new MobileViewportManager(
            mMVMContext,
            MobileViewportManager::ManagerType::VisualAndMetaViewport)) {
    mMVMContext->SetMVM(mMVM.get());
  }

  void Resize(const LayoutDeviceIntSize& aNewDisplaySize) {
    mMVMContext->SetDisplaySize(aNewDisplaySize);
    mMVM->RequestReflow(false);
  }

 protected:
  RefPtr<MockMVMContext> mMVMContext;
  RefPtr<MobileViewportManager> mMVM;
};

TEST_F(MVMTester, ZoomBoundsRespectedAfterRotation_Bug1536755) {
  // Set up initial conditions.
  mMVMContext->SetDisplaySize(LayoutDeviceIntSize(600, 300));
  mMVMContext->SetInitialScale(CSSToScreenScale(1.0f));
  mMVMContext->SetMinScale(CSSToScreenScale(1.0f));
  mMVMContext->SetMaxScale(CSSToScreenScale(1.0f));
  // Set a layout function that simulates a page which is twice
  // as tall as it is wide.
  mMVMContext->SetLayoutFunction([](CSSSize aICBSize) {
    return CSSSize(aICBSize.width, aICBSize.width * 2);
  });

  // Perform an initial viewport computation and reflow, and
  // sanity-check the results.
  mMVM->SetInitialViewport();
  EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize());
  EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize());
  EXPECT_EQ(1.0f, mMVMContext->GetResolution());

  // Now rotate the screen, and check that the minimum and maximum
  // scales are still respected after the rotation.
  Resize(LayoutDeviceIntSize(300, 600));
  EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize());
  EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetContentSize());
  EXPECT_EQ(1.0f, mMVMContext->GetResolution());
}

TEST_F(MVMTester, LandscapeToPortraitRotation_Bug1523844) {
  // Set up initial conditions.
  mMVMContext->SetDisplaySize(LayoutDeviceIntSize(300, 600));
  // Set a layout function that simulates a page with a fixed
  // content size that's as wide as the screen in one orientation
  // (and wider in the other).
  mMVMContext->SetLayoutFunction(
      [](CSSSize aICBSize) { return CSSSize(600, 1200); });

  // Simulate a "DOMMetaAdded" event being fired before calling
  // SetInitialViewport(). This matches what typically happens
  // during real usage (the MVM receives the "DOMMetaAdded"
  // before the "load", and it's the "load" that calls
  // SetInitialViewport()), and is important to trigger this
  // bug, because it causes the MVM to be stuck with an
  // "mRestoreResolution" (prior to the fix).
  mMVM->HandleDOMMetaAdded();

  // Perform an initial viewport computation and reflow, and
  // sanity-check the results.
  mMVM->SetInitialViewport();
  EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize());
  EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize());
  EXPECT_EQ(0.5f, mMVMContext->GetResolution());

  // Rotate to landscape.
  Resize(LayoutDeviceIntSize(600, 300));
  EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize());
  EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize());
  EXPECT_EQ(1.0f, mMVMContext->GetResolution());

  // Rotate back to portrait and check that we have returned
  // to the portrait resolution.
  Resize(LayoutDeviceIntSize(300, 600));
  EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize());
  EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize());
  EXPECT_EQ(0.5f, mMVMContext->GetResolution());
}