TestMobileViewportManager.cpp (9024B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 #include "gmock/gmock.h" 9 10 #include <functional> 11 12 #include "MobileViewportManager.h" 13 #include "mozilla/MVMContext.h" 14 #include "mozilla/dom/Event.h" 15 #include "mozilla/dom/InteractiveWidget.h" 16 17 using namespace mozilla; 18 19 class MockMVMContext : public MVMContext { 20 using AutoSizeFlag = nsViewportInfo::AutoSizeFlag; 21 using AutoScaleFlag = nsViewportInfo::AutoScaleFlag; 22 using ZoomFlag = nsViewportInfo::ZoomFlag; 23 24 // A "layout function" is a function that computes the content size 25 // as a function of the ICB size. 26 using LayoutFunction = std::function<CSSSize(CSSSize aICBSize)>; 27 28 public: 29 // MVMContext methods we don't care to implement. 30 MOCK_METHOD3(AddEventListener, 31 void(const nsAString& aType, nsIDOMEventListener* aListener, 32 bool aUseCapture)); 33 MOCK_METHOD3(RemoveEventListener, 34 void(const nsAString& aType, nsIDOMEventListener* aListener, 35 bool aUseCapture)); 36 MOCK_METHOD3(AddObserver, void(nsIObserver* aObserver, const char* aTopic, 37 bool aOwnsWeak)); 38 MOCK_METHOD2(RemoveObserver, 39 void(nsIObserver* aObserver, const char* aTopic)); 40 MOCK_METHOD0(Destroy, void()); 41 42 MOCK_METHOD1(SetVisualViewportSize, void(const CSSSize& aSize)); 43 MOCK_METHOD0(PostVisualViewportResizeEventByDynamicToolbar, void()); 44 MOCK_METHOD0(UpdateDisplayPortMargins, void()); 45 MOCK_METHOD0(GetDynamicToolbarOffset, ScreenIntCoord()); 46 MOCK_CONST_METHOD0(GetInteractiveWidgetMode, dom::InteractiveWidget()); 47 48 void SetMVM(MobileViewportManager* aMVM) { mMVM = aMVM; } 49 50 // MVMContext method implementations. 51 nsViewportInfo GetViewportInfo(const ScreenIntSize& aDisplaySize) const { 52 // This is a very basic approximation of what Document::GetViewportInfo() 53 // does in the most common cases. 54 // Ideally, we would invoke the algorithm in Document::GetViewportInfo() 55 // itself, but that would require refactoring it a bit to remove 56 // dependencies on the actual Document which we don't have available in 57 // this test harness. 58 CSSSize viewportSize = mDisplaySize / mDeviceScale; 59 if (mAutoSizeFlag == AutoSizeFlag::FixedSize) { 60 viewportSize = CSSSize(mFixedViewportWidth, 61 mFixedViewportWidth * (float(mDisplaySize.height) / 62 mDisplaySize.width)); 63 } 64 return nsViewportInfo(mDefaultScale, mMinScale, mMaxScale, viewportSize, 65 mAutoSizeFlag, mAutoScaleFlag, mZoomFlag, 66 dom::ViewportFitType::Auto); 67 } 68 CSSToLayoutDeviceScale CSSToDevPixelScale() const { return mDeviceScale; } 69 float GetResolution() const { return mResolution; } 70 bool SubjectMatchesDocument(nsISupports* aSubject) const { return true; } 71 Maybe<CSSRect> CalculateScrollableRectForRSF() const { 72 return Some(CSSRect(CSSPoint(), mContentSize)); 73 } 74 bool IsResolutionUpdatedByApz() const { return false; } 75 LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() const { 76 return LayoutDeviceMargin(); 77 } 78 Maybe<LayoutDeviceIntSize> GetDocumentViewerSize() const { 79 return Some(mDisplaySize); 80 } 81 bool AllowZoomingForDocument() const { return true; } 82 bool IsInReaderMode() const { return false; } 83 bool IsDocumentLoading() const { return false; } 84 bool IsDocumentFullscreen() const { return false; } 85 86 void SetResolutionAndScaleTo(float aResolution, 87 ResolutionChangeOrigin aOrigin) { 88 mResolution = aResolution; 89 mMVM->ResolutionUpdated(aOrigin); 90 } 91 void Reflow(const CSSSize& aNewSize) { 92 mICBSize = aNewSize; 93 mContentSize = mLayoutFunction(mICBSize); 94 } 95 96 // Allow test code to modify the input metrics. 97 void SetMinScale(CSSToScreenScale aMinScale) { mMinScale = aMinScale; } 98 void SetMaxScale(CSSToScreenScale aMaxScale) { mMaxScale = aMaxScale; } 99 void SetInitialScale(CSSToScreenScale aInitialScale) { 100 mDefaultScale = aInitialScale; 101 mAutoScaleFlag = AutoScaleFlag::FixedScale; 102 } 103 void SetFixedViewportWidth(CSSCoord aWidth) { 104 mFixedViewportWidth = aWidth; 105 mAutoSizeFlag = AutoSizeFlag::FixedSize; 106 } 107 void SetDisplaySize(const LayoutDeviceIntSize& aNewDisplaySize) { 108 mDisplaySize = aNewDisplaySize; 109 } 110 void SetLayoutFunction(const LayoutFunction& aLayoutFunction) { 111 mLayoutFunction = aLayoutFunction; 112 } 113 114 // Allow test code to query the output metrics. 115 CSSSize GetICBSize() const { return mICBSize; } 116 CSSSize GetContentSize() const { return mContentSize; } 117 118 private: 119 // Input metrics, with some sensible defaults. 120 LayoutDeviceIntSize mDisplaySize{300, 600}; 121 CSSToScreenScale mDefaultScale{1.0f}; 122 CSSToScreenScale mMinScale{0.25f}; 123 CSSToScreenScale mMaxScale{10.0f}; 124 CSSToLayoutDeviceScale mDeviceScale{1.0f}; 125 CSSCoord mFixedViewportWidth; 126 AutoSizeFlag mAutoSizeFlag = AutoSizeFlag::AutoSize; 127 AutoScaleFlag mAutoScaleFlag = AutoScaleFlag::AutoScale; 128 ZoomFlag mZoomFlag = ZoomFlag::AllowZoom; 129 // As a default layout function, just set the content size to the ICB size. 130 LayoutFunction mLayoutFunction = [](CSSSize aICBSize) { return aICBSize; }; 131 132 // Output metrics. 133 float mResolution = 1.0f; 134 CSSSize mICBSize; 135 CSSSize mContentSize; 136 137 MobileViewportManager* mMVM = nullptr; 138 }; 139 140 class MVMTester : public ::testing::Test { 141 public: 142 MVMTester() 143 : mMVMContext(new MockMVMContext()), 144 mMVM(new MobileViewportManager( 145 mMVMContext, 146 MobileViewportManager::ManagerType::VisualAndMetaViewport)) { 147 mMVMContext->SetMVM(mMVM.get()); 148 } 149 150 void Resize(const LayoutDeviceIntSize& aNewDisplaySize) { 151 mMVMContext->SetDisplaySize(aNewDisplaySize); 152 mMVM->RequestReflow(false); 153 } 154 155 protected: 156 RefPtr<MockMVMContext> mMVMContext; 157 RefPtr<MobileViewportManager> mMVM; 158 }; 159 160 TEST_F(MVMTester, ZoomBoundsRespectedAfterRotation_Bug1536755) { 161 // Set up initial conditions. 162 mMVMContext->SetDisplaySize(LayoutDeviceIntSize(600, 300)); 163 mMVMContext->SetInitialScale(CSSToScreenScale(1.0f)); 164 mMVMContext->SetMinScale(CSSToScreenScale(1.0f)); 165 mMVMContext->SetMaxScale(CSSToScreenScale(1.0f)); 166 // Set a layout function that simulates a page which is twice 167 // as tall as it is wide. 168 mMVMContext->SetLayoutFunction([](CSSSize aICBSize) { 169 return CSSSize(aICBSize.width, aICBSize.width * 2); 170 }); 171 172 // Perform an initial viewport computation and reflow, and 173 // sanity-check the results. 174 mMVM->SetInitialViewport(); 175 EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); 176 EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); 177 EXPECT_EQ(1.0f, mMVMContext->GetResolution()); 178 179 // Now rotate the screen, and check that the minimum and maximum 180 // scales are still respected after the rotation. 181 Resize(LayoutDeviceIntSize(300, 600)); 182 EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); 183 EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetContentSize()); 184 EXPECT_EQ(1.0f, mMVMContext->GetResolution()); 185 } 186 187 TEST_F(MVMTester, LandscapeToPortraitRotation_Bug1523844) { 188 // Set up initial conditions. 189 mMVMContext->SetDisplaySize(LayoutDeviceIntSize(300, 600)); 190 // Set a layout function that simulates a page with a fixed 191 // content size that's as wide as the screen in one orientation 192 // (and wider in the other). 193 mMVMContext->SetLayoutFunction( 194 [](CSSSize aICBSize) { return CSSSize(600, 1200); }); 195 196 // Simulate a "DOMMetaAdded" event being fired before calling 197 // SetInitialViewport(). This matches what typically happens 198 // during real usage (the MVM receives the "DOMMetaAdded" 199 // before the "load", and it's the "load" that calls 200 // SetInitialViewport()), and is important to trigger this 201 // bug, because it causes the MVM to be stuck with an 202 // "mRestoreResolution" (prior to the fix). 203 mMVM->HandleDOMMetaAdded(); 204 205 // Perform an initial viewport computation and reflow, and 206 // sanity-check the results. 207 mMVM->SetInitialViewport(); 208 EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); 209 EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); 210 EXPECT_EQ(0.5f, mMVMContext->GetResolution()); 211 212 // Rotate to landscape. 213 Resize(LayoutDeviceIntSize(600, 300)); 214 EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); 215 EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); 216 EXPECT_EQ(1.0f, mMVMContext->GetResolution()); 217 218 // Rotate back to portrait and check that we have returned 219 // to the portrait resolution. 220 Resize(LayoutDeviceIntSize(300, 600)); 221 EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); 222 EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); 223 EXPECT_EQ(0.5f, mMVMContext->GetResolution()); 224 }