From ed365c4387e7751a36b03619c5f3cb7f5f65a4b7 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Tue, 21 Jul 2015 10:51:55 -0400 Subject: [PATCH] Bug 1178847 - Add a MobileViewportManager to manage setting the CSS viewport on B2G. r=botond,tn The MobileViewportManager ("MVM") is responsible for setting the CSS viewport on any of the following events: - a page is painted for the first time (on the before-first-paint event) - a meta-viewport tag is added (on the DOMMetaAdded event) - the full-zoom is changed (on the FullZoomChanged event) - if the window is resized (ResizeReflow gets called as part of normal layout processing, and this will pick up a new CSS viewport from MVM) If the CSS viewport changes or if it is the initial paint, the MVM additionally calls SetResolutionAndScaleTo on the presShell to update the displayed zoom. The APZ code in AsyncPanZoomController::NotifyLayersUpdated already has corresponding code to accept this updated zoom when the CSS viewport changes. --- dom/ipc/TabChild.cpp | 454 +------------------------- dom/ipc/TabChild.h | 25 +- layout/base/MobileViewportManager.cpp | 281 ++++++++++++++++ layout/base/MobileViewportManager.h | 62 ++++ layout/base/moz.build | 1 + layout/base/nsIPresShell.h | 11 +- layout/base/nsPresShell.cpp | 29 ++ layout/base/nsPresShell.h | 10 +- 8 files changed, 401 insertions(+), 472 deletions(-) create mode 100644 layout/base/MobileViewportManager.cpp create mode 100644 layout/base/MobileViewportManager.h diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index c559d66c8768..dc5a19e6a135 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -165,8 +165,7 @@ NS_IMPL_ISUPPORTS(TabChild::DelayedFireContextMenuEvent, nsITimerCallback) TabChildBase::TabChildBase() - : mContentDocumentIsDisplayed(false) - , mTabChildGlobal(nullptr) + : mTabChildGlobal(nullptr) { mozilla::HoldJSObjects(this); } @@ -206,280 +205,6 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(TabChildBase) NS_IMPL_CYCLE_COLLECTING_RELEASE(TabChildBase) -// For the root frame, Screen and ParentLayer pixels are interchangeable. -// nsViewportInfo stores zoom values as CSSToScreenScale (because it's a -// data structure specific to the root frame), while FrameMetrics and -// ZoomConstraints store zoom values as CSSToParentLayerScale (because they -// are not specific to the root frame). We define convenience functions for -// converting between the two. As the name suggests, they should only be used -// when dealing with the root frame! -CSSToScreenScale ConvertScaleForRoot(CSSToParentLayerScale aScale) -{ - return ViewTargetAs(aScale, PixelCastJustification::ScreenIsParentLayerForRoot); -} -CSSToParentLayerScale ConvertScaleForRoot(CSSToScreenScale aScale) -{ - return ViewTargetAs(aScale, PixelCastJustification::ScreenIsParentLayerForRoot); -} - -// Calculate the scale needed to fit the given viewport into the given display. -CSSToScreenScale CalculateIntrinsicScale(const ScreenIntSize& aDisplaySize, const CSSSize& aViewportSize) -{ - return MaxScaleRatio(ScreenSize(aDisplaySize), aViewportSize); -} - -void -TabChildBase::InitializeRootMetrics() -{ - // Calculate a really simple resolution that we probably won't - // be keeping, as well as putting the scroll offset back to - // the top-left of the page. - mLastRootMetrics.SetViewport(CSSRect(CSSPoint(), kDefaultViewportSize)); - mLastRootMetrics.SetCompositionBounds(ParentLayerRect( - ParentLayerPoint(), - ParentLayerSize( - ViewAs(GetInnerSize(), - PixelCastJustification::ScreenIsParentLayerForRoot)))); - mLastRootMetrics.SetZoom(CSSToParentLayerScale2D( - ConvertScaleForRoot(CalculateIntrinsicScale(GetInnerSize(), kDefaultViewportSize)))); - mLastRootMetrics.SetDevPixelsPerCSSPixel(WebWidget()->GetDefaultScale()); - // We use ParentLayerToLayerScale(1) below in order to turn the - // async zoom amount into the gecko zoom amount. - mLastRootMetrics.SetCumulativeResolution(mLastRootMetrics.GetZoom() / mLastRootMetrics.GetDevPixelsPerCSSPixel() * ParentLayerToLayerScale(1)); - // This is the root layer, so the cumulative resolution is the same - // as the resolution. - mLastRootMetrics.SetPresShellResolution(mLastRootMetrics.GetCumulativeResolution().ToScaleFactor().scale); - - nsCOMPtr shell = GetPresShell(); - if (shell && shell->GetRootScrollFrameAsScrollable()) { - // The session history code might restore a scroll position when navigating - // back or forward, and we don't want to clobber that. - nsPoint pos = shell->GetRootScrollFrameAsScrollable()->GetScrollPosition(); - mLastRootMetrics.SetScrollOffset(CSSPoint::FromAppUnits(pos)); - } else { - mLastRootMetrics.SetScrollOffset(CSSPoint(0, 0)); - } - - TABC_LOG("After InitializeRootMetrics, mLastRootMetrics is %s\n", - Stringify(mLastRootMetrics).c_str()); -} - -void -TabChildBase::SetCSSViewport(const CSSSize& aSize) -{ - mOldViewportSize = aSize; - TABC_LOG("Setting CSS viewport to %s\n", Stringify(aSize).c_str()); - - if (mContentDocumentIsDisplayed) { - if (nsCOMPtr shell = GetPresShell()) { - nsLayoutUtils::SetCSSViewport(shell, aSize); - } - } -} - -CSSSize -TabChildBase::GetPageSize(nsCOMPtr aDocument, const CSSSize& aViewport) -{ - nsCOMPtr htmlDOMElement = aDocument->GetHtmlElement(); - HTMLBodyElement* bodyDOMElement = aDocument->GetBodyElement(); - - if (!htmlDOMElement && !bodyDOMElement) { - // For non-HTML content (e.g. SVG), just assume page size == viewport size. - return aViewport; - } - - int32_t htmlWidth = 0, htmlHeight = 0; - if (htmlDOMElement) { - htmlWidth = htmlDOMElement->ScrollWidth(); - htmlHeight = htmlDOMElement->ScrollHeight(); - } - int32_t bodyWidth = 0, bodyHeight = 0; - if (bodyDOMElement) { - bodyWidth = bodyDOMElement->ScrollWidth(); - bodyHeight = bodyDOMElement->ScrollHeight(); - } - return CSSSize(std::max(htmlWidth, bodyWidth), - std::max(htmlHeight, bodyHeight)); -} - -bool -TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize) -{ - PuppetWidget* widget = WebWidget(); - if (!widget || !widget->AsyncPanZoomEnabled()) { - return false; - } - - TABC_LOG("HandlePossibleViewportChange aOldScreenSize=%s mInnerSize=%s\n", - Stringify(aOldScreenSize).c_str(), Stringify(GetInnerSize()).c_str()); - - nsCOMPtr document(GetDocument()); - if (!document) { - return false; - } - - nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, GetInnerSize()); - uint32_t presShellId = 0; - mozilla::layers::FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID; - APZCCallbackHelper::GetOrCreateScrollIdentifiers( - document->GetDocumentElement(), &presShellId, &viewId); - - float screenW = GetInnerSize().width; - float screenH = GetInnerSize().height; - CSSSize viewport(viewportInfo.GetSize()); - - // We're not being displayed in any way; don't bother doing anything because - // that will just confuse future adjustments. - if (!screenW || !screenH) { - return false; - } - - TABC_LOG("HandlePossibleViewportChange mOldViewportSize=%s viewport=%s\n", - Stringify(mOldViewportSize).c_str(), Stringify(viewport).c_str()); - CSSSize oldBrowserSize = mOldViewportSize; - mLastRootMetrics.SetViewport(CSSRect( - mLastRootMetrics.GetViewport().TopLeft(), viewport)); - if (oldBrowserSize == CSSSize()) { - oldBrowserSize = kDefaultViewportSize; - } - SetCSSViewport(viewport); - - // If this page has not been painted yet, then this must be getting run - // because a meta-viewport element was added (via the DOMMetaAdded handler). - // in this case, we should not do anything that forces a reflow (see bug - // 759678) such as requesting the page size or sending a viewport update. this - // code will get run again in the before-first-paint handler and that point we - // will run though all of it. the reason we even bother executing up to this - // point on the DOMMetaAdded handler is so that scripts that use - // window.innerWidth before they are painted have a correct value (bug - // 771575). - if (!mContentDocumentIsDisplayed) { - return false; - } - - ScreenIntSize oldScreenSize = aOldScreenSize; - if (oldScreenSize == ScreenIntSize()) { - oldScreenSize = GetInnerSize(); - } - - FrameMetrics metrics(mLastRootMetrics); - metrics.SetViewport(CSSRect(CSSPoint(), viewport)); - - // Calculate the composition bounds based on the inner size, excluding the sizes - // of the scrollbars if they are not overlay scrollbars. - ScreenSize compositionSize(GetInnerSize()); - nsCOMPtr shell = GetPresShell(); - if (shell) { - nsMargin scrollbarsAppUnits = - nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(shell->GetRootScrollFrame()); - // Scrollbars are not subject to scaling, so CSS pixels = screen pixels for them. - ScreenMargin scrollbars = CSSMargin::FromAppUnits(scrollbarsAppUnits) - * CSSToScreenScale(1.0f); - compositionSize.width -= scrollbars.LeftRight(); - compositionSize.height -= scrollbars.TopBottom(); - } - - metrics.SetCompositionBounds(ParentLayerRect( - ParentLayerPoint(), - ParentLayerSize( - ViewAs(GetInnerSize(), - PixelCastJustification::ScreenIsParentLayerForRoot)))); - metrics.SetRootCompositionSize( - ScreenSize(compositionSize) * ScreenToLayoutDeviceScale(1.0f) / metrics.GetDevPixelsPerCSSPixel()); - - // This change to the zoom accounts for all types of changes I can conceive: - // 1. screen size changes, CSS viewport does not (pages with no meta viewport - // or a fixed size viewport) - // 2. screen size changes, CSS viewport also does (pages with a device-width - // viewport) - // 3. screen size remains constant, but CSS viewport changes (meta viewport - // tag is added or removed) - // 4. neither screen size nor CSS viewport changes - // - // In all of these cases, we maintain how much actual content is visible - // within the screen width. Note that "actual content" may be different with - // respect to CSS pixels because of the CSS viewport size changing. - CSSToScreenScale oldIntrinsicScale = CalculateIntrinsicScale(oldScreenSize, oldBrowserSize); - CSSToScreenScale newIntrinsicScale = CalculateIntrinsicScale(GetInnerSize(), viewport); - metrics.ZoomBy(newIntrinsicScale.scale / oldIntrinsicScale.scale); - - // Changing the zoom when we're not doing a first paint will get ignored - // by AsyncPanZoomController and causes a blurry flash. - bool isFirstPaint = true; - if (shell) { - isFirstPaint = shell->GetIsFirstPaint(); - } - if (isFirstPaint) { - // FIXME/bug 799585(?): GetViewportInfo() returns a defaultZoom of - // 0.0 to mean "did not calculate a zoom". In that case, we default - // it to the intrinsic scale. - if (viewportInfo.GetDefaultZoom().scale < 0.01f) { - viewportInfo.SetDefaultZoom(newIntrinsicScale); - } - - CSSToScreenScale defaultZoom = viewportInfo.GetDefaultZoom(); - MOZ_ASSERT(viewportInfo.GetMinZoom() <= defaultZoom && - defaultZoom <= viewportInfo.GetMaxZoom()); - metrics.SetZoom(CSSToParentLayerScale2D(ConvertScaleForRoot(defaultZoom))); - - metrics.SetPresShellId(presShellId); - metrics.SetScrollId(viewId); - } - - if (shell) { - if (nsPresContext* context = shell->GetPresContext()) { - metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale( - (float)nsPresContext::AppUnitsPerCSSPixel() / context->AppUnitsPerDevPixel())); - } - } - - metrics.SetCumulativeResolution(metrics.GetZoom() - / metrics.GetDevPixelsPerCSSPixel() - * ParentLayerToLayerScale(1)); - // This is the root layer, so the cumulative resolution is the same - // as the resolution. - metrics.SetPresShellResolution(metrics.GetCumulativeResolution().ToScaleFactor().scale); - if (shell) { - nsLayoutUtils::SetResolutionAndScaleTo(shell, metrics.GetPresShellResolution()); - nsLayoutUtils::SetScrollPositionClampingScrollPortSize(shell, - metrics.CalculateCompositedSizeInCssPixels()); - } - - // The call to GetPageSize forces a resize event to content, so we need to - // make sure that we have the right CSS viewport and - // scrollPositionClampingScrollPortSize set up before that happens. - - CSSSize pageSize = GetPageSize(document, viewport); - if (!pageSize.width) { - // Return early rather than divide by 0. - return false; - } - metrics.SetScrollableRect(CSSRect(CSSPoint(), pageSize)); - - // Calculate a display port _after_ having a scrollable rect because the - // display port is clamped to the scrollable rect. - metrics.SetDisplayPortMargins(APZCTreeManager::CalculatePendingDisplayPort( - // The page must have been refreshed in some way such as a new document or - // new CSS viewport, so we know that there's no velocity, acceleration, and - // we have no idea how long painting will take. - metrics, ParentLayerPoint(0.0f, 0.0f), 0.0)); - metrics.SetUseDisplayPortMargins(); - - // Force a repaint with these metrics. This, among other things, sets the - // displayport, so we start with async painting. - mLastRootMetrics = ProcessUpdateFrame(metrics); - - return true; -} - -already_AddRefed -TabChildBase::GetDOMWindowUtils() -{ - nsCOMPtr window = do_GetInterface(WebNavigation()); - nsCOMPtr utils = do_GetInterface(window); - return utils.forget(); -} - already_AddRefed TabChildBase::GetDocument() const { @@ -535,7 +260,7 @@ TabChildBase::UpdateFrameHandler(const FrameMetrics& aFrameMetrics) // Guard against stale updates (updates meant for a pres shell which // has since been torn down and destroyed). if (aFrameMetrics.GetPresShellId() == shell->GetPresShellId()) { - mLastRootMetrics = ProcessUpdateFrame(aFrameMetrics); + ProcessUpdateFrame(aFrameMetrics); return true; } } @@ -549,11 +274,11 @@ TabChildBase::UpdateFrameHandler(const FrameMetrics& aFrameMetrics) return true; } -FrameMetrics +void TabChildBase::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics) { if (!mGlobal || !mTabChildGlobal) { - return aFrameMetrics; + return; } FrameMetrics newMetrics = aFrameMetrics; @@ -591,7 +316,6 @@ TabChildBase::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics) data.AppendLiteral(" }"); DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data); - return newMetrics; } NS_IMETHODIMP @@ -909,22 +633,6 @@ TabChild::TabChild(nsIContentChild* aManager, } } -NS_IMETHODIMP -TabChild::HandleEvent(nsIDOMEvent* aEvent) -{ - nsAutoString eventType; - aEvent->GetType(eventType); - if (eventType.EqualsLiteral("DOMMetaAdded")) { - // This meta data may or may not have been a meta viewport tag. If it was, - // we should handle it immediately. - HandlePossibleViewportChange(GetInnerSize()); - } else if (eventType.EqualsLiteral("FullZoomChange")) { - HandlePossibleViewportChange(GetInnerSize()); - } - - return NS_OK; -} - NS_IMETHODIMP TabChild::Observe(nsISupports *aSubject, const char *aTopic, @@ -957,19 +665,7 @@ TabChild::Observe(nsISupports *aSubject, shell->SetIsFirstPaint(true); } - mContentDocumentIsDisplayed = true; - - // In some cases before-first-paint gets called before - // RecvUpdateDimensions is called and therefore before we have an - // inner size value set. In such cases defer initializing the viewport - // until we we get an inner size. - if (HasValidInnerSize()) { - InitializeRootMetrics(); - if (shell) { - nsLayoutUtils::SetResolutionAndScaleTo(shell, mLastRootMetrics.GetPresShellResolution()); - } - HandlePossibleViewportChange(GetInnerSize()); - } + APZCCallbackHelper::InitializeRootDisplayport(shell); } } } @@ -1027,100 +723,6 @@ TabChild::Observe(nsISupports *aSubject, return NS_OK; } -NS_IMETHODIMP -TabChild::OnStateChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, - uint32_t aStateFlags, - nsresult aStatus) -{ - NS_NOTREACHED("not implemented in TabChild"); - return NS_OK; -} - -NS_IMETHODIMP -TabChild::OnProgressChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, - int32_t aCurSelfProgress, - int32_t aMaxSelfProgress, - int32_t aCurTotalProgress, - int32_t aMaxTotalProgress) -{ - NS_NOTREACHED("not implemented in TabChild"); - return NS_OK; -} - -NS_IMETHODIMP -TabChild::OnLocationChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, - nsIURI *aLocation, - uint32_t aFlags) -{ - if (!AsyncPanZoomEnabled()) { - return NS_OK; - } - - nsCOMPtr window; - aWebProgress->GetDOMWindow(getter_AddRefs(window)); - if (!window) { - return NS_OK; - } - - nsCOMPtr progressDoc; - window->GetDocument(getter_AddRefs(progressDoc)); - if (!progressDoc) { - return NS_OK; - } - - nsCOMPtr domDoc; - WebNavigation()->GetDocument(getter_AddRefs(domDoc)); - if (!domDoc || !SameCOMIdentity(domDoc, progressDoc)) { - return NS_OK; - } - - nsCOMPtr urifixup(do_GetService(NS_URIFIXUP_CONTRACTID)); - if (!urifixup) { - return NS_OK; - } - - nsCOMPtr exposableURI; - urifixup->CreateExposableURI(aLocation, getter_AddRefs(exposableURI)); - if (!exposableURI) { - return NS_OK; - } - - if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { - mContentDocumentIsDisplayed = false; - } else if (mLastURI != nullptr) { - bool exposableEqualsLast, exposableEqualsNew; - exposableURI->Equals(mLastURI.get(), &exposableEqualsLast); - exposableURI->Equals(aLocation, &exposableEqualsNew); - if (exposableEqualsLast && !exposableEqualsNew) { - mContentDocumentIsDisplayed = false; - } - } - - return NS_OK; -} - -NS_IMETHODIMP -TabChild::OnStatusChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, - nsresult aStatus, - const char16_t* aMessage) -{ - NS_NOTREACHED("not implemented in TabChild"); - return NS_OK; -} - -NS_IMETHODIMP -TabChild::OnSecurityChange(nsIWebProgress* aWebProgress, - nsIRequest* aRequest, - uint32_t aState) -{ - NS_NOTREACHED("not implemented in TabChild"); - return NS_OK; -} - bool TabChild::DoUpdateZoomConstraints(const uint32_t& aPresShellId, const ViewID& aViewId, @@ -1201,10 +803,6 @@ TabChild::Init() loadContext->SetRemoteTabs( mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW); - nsCOMPtr webProgress = do_GetInterface(docShell); - NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE); - webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION); - // Few lines before, baseWindow->Create() will end up creating a new // window root in nsGlobalWindow::SetDocShell. // Then this chrome event handler, will be inherited to inner windows. @@ -1249,8 +847,6 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TabChild) NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) - NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) - NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) NS_INTERFACE_MAP_ENTRY(nsITabChild) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) @@ -1651,12 +1247,6 @@ TabChild::ProvideWindowCommon(nsIDOMWindow* aOpener, return NS_OK; } -bool -TabChild::HasValidInnerSize() -{ - return mHasValidInnerSize; -} - void TabChild::DestroyWindow() { @@ -2116,40 +1706,25 @@ TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size, mUnscaledOuterRect = rect; mChromeDisp = chromeDisp; - bool initialSizing = !HasValidInnerSize() - && (size.width != 0 && size.height != 0); - mOrientation = orientation; - ScreenIntSize oldScreenSize = GetInnerSize(); SetUnscaledInnerSize(size); - ScreenIntSize screenSize = GetInnerSize(); - bool sizeChanged = true; - if (initialSizing) { + if (!mHasValidInnerSize && size.width != 0 && size.height != 0) { mHasValidInnerSize = true; - } else if (screenSize == oldScreenSize) { - sizeChanged = false; } + ScreenIntSize screenSize = GetInnerSize(); ScreenIntRect screenRect = GetOuterRect(); - mPuppetWidget->Resize(screenRect.x + chromeDisp.x, - screenRect.y + chromeDisp.y, - screenSize.width, screenSize.height, true); + // Set the size on the document viewer before we update the widget and + // trigger a reflow. Otherwise the MobileViewportManager reads the stale + // size from the content viewer when it computes a new CSS viewport. nsCOMPtr baseWin = do_QueryInterface(WebNavigation()); baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height, true); - if (initialSizing && mContentDocumentIsDisplayed) { - // If this is the first time we're getting a valid inner size, and the - // before-first-paint event has already been handled, then we need to set - // up our default viewport here. See the corresponding call to - // InitializeRootMetrics in the before-first-paint handler. - InitializeRootMetrics(); - } - - if (sizeChanged) { - HandlePossibleViewportChange(oldScreenSize); - } + mPuppetWidget->Resize(screenRect.x + chromeDisp.x, + screenRect.y + chromeDisp.y, + screenSize.width, screenSize.height, true); return true; } @@ -3001,9 +2576,6 @@ TabChild::InitTabChildGlobal(FrameScriptLoading aScriptLoading) nsCOMPtr root = do_QueryInterface(chromeHandler); NS_ENSURE_TRUE(root, false); root->SetParentTarget(scope); - - chromeHandler->AddEventListener(NS_LITERAL_STRING("DOMMetaAdded"), this, false); - chromeHandler->AddEventListener(NS_LITERAL_STRING("FullZoomChange"), this, false); } if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) { diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 033982431bb9..30f4efd31e5e 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -21,7 +21,6 @@ #include "nsIDocShell.h" #include "nsIInterfaceRequestorUtils.h" #include "nsFrameMessageManager.h" -#include "nsIWebProgressListener.h" #include "nsIPresShell.h" #include "nsIScriptObjectPrincipal.h" #include "nsWeakReference.h" @@ -183,12 +182,6 @@ public: virtual nsIWebNavigation* WebNavigation() const = 0; virtual PuppetWidget* WebWidget() = 0; nsIPrincipal* GetPrincipal() { return mPrincipal; } - // Recalculates the display state, including the CSS - // viewport. This should be called whenever we believe the - // viewport data on a document may have changed. If it didn't - // change, this function doesn't do anything. However, it should - // not be called all the time as it is fairly expensive. - bool HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize); virtual bool DoUpdateZoomConstraints(const uint32_t& aPresShellId, const mozilla::layers::FrameMetrics::ViewID& aViewId, const Maybe& aConstraints) = 0; @@ -197,19 +190,12 @@ public: protected: virtual ~TabChildBase(); - CSSSize GetPageSize(nsCOMPtr aDocument, const CSSSize& aViewport); - // Get the DOMWindowUtils for the top-level window in this tab. - already_AddRefed GetDOMWindowUtils(); // Get the Document for the top-level window in this tab. already_AddRefed GetDocument() const; // Get the pres-shell of the document for the top-level window in this tab. already_AddRefed GetPresShell() const; - // Wrapper for nsIDOMWindowUtils.setCSSViewport(). This updates some state - // variables local to this class before setting it. - void SetCSSViewport(const CSSSize& aSize); - // Wraps up a JSON object as a structured clone and sends it to the browser // chrome script. // @@ -218,17 +204,12 @@ protected: void DispatchMessageManagerMessage(const nsAString& aMessageName, const nsAString& aJSONData); - void InitializeRootMetrics(); - - mozilla::layers::FrameMetrics ProcessUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics); + void ProcessUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics); bool UpdateFrameHandler(const mozilla::layers::FrameMetrics& aFrameMetrics); protected: - CSSSize mOldViewportSize; - bool mContentDocumentIsDisplayed; nsRefPtr mTabChildGlobal; - mozilla::layers::FrameMetrics mLastRootMetrics; nsCOMPtr mWebBrowserChrome; }; @@ -239,8 +220,6 @@ class TabChild final : public TabChildBase, public nsIWebBrowserChromeFocus, public nsIInterfaceRequestor, public nsIWindowProvider, - public nsIDOMEventListener, - public nsIWebProgressListener, public nsSupportsWeakReference, public nsITabChild, public nsIObserver, @@ -288,8 +267,6 @@ public: NS_DECL_NSIWEBBROWSERCHROMEFOCUS NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIWINDOWPROVIDER - NS_DECL_NSIDOMEVENTLISTENER - NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSITABCHILD NS_DECL_NSIOBSERVER NS_DECL_NSITOOLTIPLISTENER diff --git a/layout/base/MobileViewportManager.cpp b/layout/base/MobileViewportManager.cpp new file mode 100644 index 000000000000..f36dde556d4b --- /dev/null +++ b/layout/base/MobileViewportManager.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "MobileViewportManager.h" + +#include "LayersLogging.h" +#include "nsViewManager.h" +#include "nsViewportInfo.h" + +#define MVM_LOG(...) +// #define MVM_LOG(...) printf_stderr("MVM: " __VA_ARGS__) + +NS_IMPL_ISUPPORTS(MobileViewportManager, nsIDOMEventListener, nsIObserver) + +static const nsLiteralString DOM_META_ADDED = NS_LITERAL_STRING("DOMMetaAdded"); +static const nsLiteralString FULL_ZOOM_CHANGE = NS_LITERAL_STRING("FullZoomChange"); +static const nsLiteralCString BEFORE_FIRST_PAINT = NS_LITERAL_CSTRING("before-first-paint"); + +using namespace mozilla; +using namespace mozilla::layers; + +MobileViewportManager::MobileViewportManager(nsIPresShell* aPresShell, + nsIDocument* aDocument) + : mDocument(aDocument) + , mPresShell(aPresShell) + , mIsFirstPaint(false) +{ + MOZ_ASSERT(mPresShell); + MOZ_ASSERT(mDocument); + + MVM_LOG("%p: creating with presShell %p document %p\n", this, mPresShell, aDocument); + + if (nsCOMPtr window = mDocument->GetWindow()) { + mEventTarget = window->GetChromeEventHandler(); + } + if (mEventTarget) { + mEventTarget->AddEventListener(DOM_META_ADDED, this, false); + mEventTarget->AddEventListener(FULL_ZOOM_CHANGE, this, false); + } + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false); + } +} + +MobileViewportManager::~MobileViewportManager() +{ +} + +void +MobileViewportManager::Destroy() +{ + MVM_LOG("%p: destroying\n", this); + + if (mEventTarget) { + mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false); + mEventTarget->RemoveEventListener(FULL_ZOOM_CHANGE, this, false); + mEventTarget = nullptr; + } + + nsCOMPtr observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data()); + } + + mDocument = nullptr; + mPresShell = nullptr; +} + +void +MobileViewportManager::RequestReflow() +{ + MVM_LOG("%p: got a reflow request\n", this); + RefreshViewportSize(false); +} + +NS_IMETHODIMP +MobileViewportManager::HandleEvent(nsIDOMEvent* event) +{ + nsAutoString type; + event->GetType(type); + + if (type.Equals(DOM_META_ADDED)) { + MVM_LOG("%p: got a dom-meta-added event\n", this); + RefreshViewportSize(true); + } else if (type.Equals(FULL_ZOOM_CHANGE)) { + MVM_LOG("%p: got a full-zoom-change event\n", this); + RefreshViewportSize(false); + } + return NS_OK; +} + +NS_IMETHODIMP +MobileViewportManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + if (SameCOMIdentity(aSubject, mDocument) && BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) { + MVM_LOG("%p: got a before-first-paint event\n", this); + mIsFirstPaint = true; + RefreshViewportSize(false); + } + return NS_OK; +} + +CSSToScreenScale +MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo, + const ScreenIntSize& aDisplaySize, + const CSSSize& aViewport, + const Maybe& aDisplayWidthChangeRatio) +{ + CSSToLayoutDeviceScale cssToDev((float)nsPresContext::AppUnitsPerCSSPixel() + / mPresShell->GetPresContext()->AppUnitsPerDevPixel()); + LayoutDeviceToLayerScale res(nsLayoutUtils::GetResolution(mPresShell)); + +#if defined(MOZ_WIDGET_GONK) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) + if (mIsFirstPaint) { + CSSToScreenScale defaultZoom = aViewportInfo.GetDefaultZoom(); + MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale); + // FIXME/bug 799585(?): GetViewportInfo() returns a default zoom of + // 0.0 to mean "did not calculate a zoom". In that case, we default + // it to the intrinsic scale. + if (defaultZoom.scale < 0.01f) { + defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport); + MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale); + } + MOZ_ASSERT(aViewportInfo.GetMinZoom() <= defaultZoom && + defaultZoom <= aViewportInfo.GetMaxZoom()); + + CSSToParentLayerScale zoom = ViewTargetAs(defaultZoom, + PixelCastJustification::ScreenIsParentLayerForRoot); + + LayoutDeviceToLayerScale resolution = zoom / cssToDev * ParentLayerToLayerScale(1); + MVM_LOG("%p: setting resolution %f\n", this, resolution.scale); + nsLayoutUtils::SetResolutionAndScaleTo(mPresShell, resolution.scale); + + return defaultZoom; + } + + // If this is not a first paint, then in some cases we want to update the pre- + // existing resolution so as to maintain how much actual content is visible + // within the display width. Note that "actual content" may be different with + // respect to CSS pixels because of the CSS viewport size changing. + // + // aDisplayWidthChangeRatio is non-empty if: + // (a) The meta-viewport tag information changes, and so the CSS viewport + // might change as a result. In this case, we want to adjust the zoom to + // compensate. OR + // (b) The display size changed from a nonzero value to another nonzero value. + // This covers the case where e.g. the device was rotated, and again we + // want to adjust the zoom to compensate. + // Note in particular that aDisplayWidthChangeRatio will be None if all that + // happened was a change in the full-zoom. In this case, we still want to + // compute a new CSS viewport, but we don't want to update the resolution. + // + // Given the above, the algorithm below accounts for all types of changes I + // can conceive of: + // 1. screen size changes, CSS viewport does not (pages with no meta viewport + // or a fixed size viewport) + // 2. screen size changes, CSS viewport also does (pages with a device-width + // viewport) + // 3. screen size remains constant, but CSS viewport changes (meta viewport + // tag is added or removed) + // 4. neither screen size nor CSS viewport changes + if (aDisplayWidthChangeRatio) { + float cssViewportChangeRatio = (mMobileViewportSize.width == 0) + ? 1.0f : aViewport.width / mMobileViewportSize.width; + LayoutDeviceToLayerScale newRes(res.scale * aDisplayWidthChangeRatio.value() + / cssViewportChangeRatio); + MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, res.scale, + aDisplayWidthChangeRatio.value(), cssViewportChangeRatio, newRes.scale); + nsLayoutUtils::SetResolutionAndScaleTo(mPresShell, newRes.scale); + res = newRes; + } +#endif + + return ViewTargetAs(cssToDev * res / ParentLayerToLayerScale(1), + PixelCastJustification::ScreenIsParentLayerForRoot); +} + +void +MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize, + const CSSToScreenScale& aZoom) +{ + ScreenSize compositionSize(aDisplaySize); + ScreenMargin scrollbars = + CSSMargin::FromAppUnits( + nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor( + mPresShell->GetRootScrollFrame())) + * CSSToScreenScale(1.0f); // Scrollbars are not subject to scaling, so + // CSS pixels = layer pixels for them (modulo bug 1168487). + compositionSize.width -= scrollbars.LeftRight(); + compositionSize.height -= scrollbars.TopBottom(); + CSSSize compSize = compositionSize / aZoom; + MVM_LOG("%p: Setting SPCSPS %s\n", this, Stringify(compSize).c_str()); + nsLayoutUtils::SetScrollPositionClampingScrollPortSize(mPresShell, compSize); +} + +void +MobileViewportManager::UpdateDisplayPortMargins() +{ + if (nsIScrollableFrame* root = mPresShell->GetRootScrollFrameAsScrollable()) { + nsLayoutUtils::CalculateAndSetDisplayPortMargins(root, + nsLayoutUtils::RepaintMode::DoNotRepaint); + } +} + +void +MobileViewportManager::RefreshViewportSize(bool aForceAdjustResolution) +{ + // This function gets called by the various triggers that may result in a + // change of the CSS viewport. In some of these cases (e.g. the meta-viewport + // tag changes) we want to update the resolution and in others (e.g. the full + // zoom changing) we don't want to update the resolution. See the comment in + // UpdateResolution for some more detail on this. An important assumption we + // make here is that this RefreshViewportSize function will be called + // separately for each trigger that changes. For instance it should never get + // called such that both the full zoom and the meta-viewport tag have changed; + // instead it would get called twice - once after each trigger changes. This + // assumption is what allows the aForceAdjustResolution parameter to work as + // intended; if this assumption is violated then we will need to add extra + // complicated logic in UpdateResolution to ensure we only do the resolution + // update in the right scenarios. + + Maybe displayWidthChangeRatio; + LayoutDeviceIntSize newDisplaySize; + if (nsLayoutUtils::GetContentViewerSize(mPresShell->GetPresContext(), newDisplaySize)) { + // See the comment in UpdateResolution for why we're doing this. + if (mDisplaySize.width > 0) { + if (aForceAdjustResolution || mDisplaySize.width != newDisplaySize.width) { + displayWidthChangeRatio = Some((float)newDisplaySize.width / (float)mDisplaySize.width); + } + } else if (aForceAdjustResolution) { + displayWidthChangeRatio = Some(1.0f); + } + + MVM_LOG("%p: Display width change ratio is %f\n", this, displayWidthChangeRatio.valueOr(0.0f)); + mDisplaySize = newDisplaySize; + } + + MVM_LOG("%p: Computing CSS viewport using %d,%d\n", this, + mDisplaySize.width, mDisplaySize.height); + if (mDisplaySize.width == 0 || mDisplaySize.height == 0) { + // We can't do anything useful here, we should just bail out + return; + } + + ScreenIntSize displaySize = ViewAs( + mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); + nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo( + mDocument, displaySize); + + CSSSize viewport = viewportInfo.GetSize(); + MVM_LOG("%p: Computed CSS viewport %s\n", this, Stringify(viewport).c_str()); + + if (!mIsFirstPaint && mMobileViewportSize == viewport) { + // Nothing changed, so no need to do a reflow + return; + } + + // If it's the first-paint or the viewport changed, we need to update + // various APZ properties (the zoom and some things that might depend on it) + MVM_LOG("%p: Updating properties because %d || %d\n", this, + mIsFirstPaint, mMobileViewportSize != viewport); + + CSSToScreenScale zoom = UpdateResolution(viewportInfo, displaySize, viewport, + displayWidthChangeRatio); + MVM_LOG("%p: New zoom is %f\n", this, zoom.scale); + UpdateSPCSPS(displaySize, zoom); + UpdateDisplayPortMargins(); + + // Update internal state. + mIsFirstPaint = false; + mMobileViewportSize = viewport; + + // Kick off a reflow. + mPresShell->ResizeReflowIgnoreOverride( + nsPresContext::CSSPixelsToAppUnits(viewport.width), + nsPresContext::CSSPixelsToAppUnits(viewport.height)); +} diff --git a/layout/base/MobileViewportManager.h b/layout/base/MobileViewportManager.h new file mode 100644 index 000000000000..26676c602a4f --- /dev/null +++ b/layout/base/MobileViewportManager.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 MobileViewportManager_h_ +#define MobileViewportManager_h_ + +#include "mozilla/Maybe.h" +#include "nsIDOMEventListener.h" +#include "nsIDocument.h" +#include "nsIObserver.h" +#include "Units.h" + +class nsIDOMEventTarget; +class nsIDocument; +class nsIPresShell; + +class MobileViewportManager final : public nsIDOMEventListener + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIOBSERVER + + MobileViewportManager(nsIPresShell* aPresShell, + nsIDocument* aDocument); + void Destroy(); + + /* Notify the MobileViewportManager that a reflow was requested in the + * presShell.*/ + void RequestReflow(); + +private: + ~MobileViewportManager(); + + /* Main helper method to update the CSS viewport and any other properties that + * need updating. */ + void RefreshViewportSize(bool aForceAdjustResolution); + + /* Updates the presShell resolution and returns the new zoom. */ + mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo, + const mozilla::ScreenIntSize& aDisplaySize, + const mozilla::CSSSize& aViewport, + const mozilla::Maybe& aDisplayWidthChangeRatio); + /* Updates the scroll-position-clamping scrollport size */ + void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize, + const mozilla::CSSToScreenScale& aZoom); + /* Updates the displayport margins for the presShell's root scrollable frame */ + void UpdateDisplayPortMargins(); + + nsCOMPtr mDocument; + nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this + nsCOMPtr mEventTarget; + bool mIsFirstPaint; + mozilla::LayoutDeviceIntSize mDisplaySize; + mozilla::CSSSize mMobileViewportSize; +}; + +#endif + diff --git a/layout/base/moz.build b/layout/base/moz.build index ff5755fb31e8..7e8797f2e62d 100644 --- a/layout/base/moz.build +++ b/layout/base/moz.build @@ -112,6 +112,7 @@ UNIFIED_SOURCES += [ 'GeometryUtils.cpp', 'LayoutLogging.cpp', 'MaskLayerImageCache.cpp', + 'MobileViewportManager.cpp', 'nsBidi.cpp', 'nsBidiPresUtils.cpp', 'nsCaret.cpp', diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index 7040c5e08c7f..6580620bdb04 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -139,10 +139,10 @@ typedef struct CapturingContentInfo { mozilla::StaticRefPtr mContent; } CapturingContentInfo; -// 7f0ae6b1-5fa1-4ba7-885e-a93e17d72cd2 +// 4f512d0b-c58c-4fc9-ae42-8aa6d992e7ae #define NS_IPRESSHELL_IID \ -{ 0x7f0ae6b1, 0x5fa1, 0x4ba7, \ - { 0x88, 0x5e, 0xa9, 0x3e, 0x17, 0xd7, 0x2c, 0xd2 } } +{ 0x4f512d0b, 0xc58c, 0x4fc9, \ + { 0xae, 0x42, 0x8a, 0xa6, 0xd9, 0x92, 0xe7, 0xae } } // debug VerifyReflow flags #define VERIFY_REFLOW_ON 0x01 @@ -410,6 +410,11 @@ public: * ResizeReflow() calls are ignored after ResizeReflowOverride(). */ virtual nsresult ResizeReflowOverride(nscoord aWidth, nscoord aHeight) = 0; + /** + * Do the same thing as ResizeReflow but even if ResizeReflowOverride was + * called previously. + */ + virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight) = 0; /** * Returns true if ResizeReflowOverride has been called. diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 0c577eddc2b2..f8864178f68d 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -984,6 +984,9 @@ PresShell::Init(nsIDocument* aDocument, if (mPresContext->IsRootContentDocument()) { mZoomConstraintsClient = new ZoomConstraintsClient(); mZoomConstraintsClient->Init(this, mDocument); + if (gfxPrefs::MetaViewportEnabled()) { + mMobileViewportManager = new MobileViewportManager(this, mDocument); + } } } @@ -1102,6 +1105,10 @@ PresShell::Destroy() mZoomConstraintsClient->Destroy(); mZoomConstraintsClient = nullptr; } + if (mMobileViewportManager) { + mMobileViewportManager->Destroy(); + mMobileViewportManager = nullptr; + } #ifdef ACCESSIBILITY if (mDocAccessible) { @@ -1752,6 +1759,19 @@ nsresult PresShell::ResizeReflowOverride(nscoord aWidth, nscoord aHeight) { mViewportOverridden = true; + + if (mMobileViewportManager) { + // Once the viewport is explicitly overridden, we don't need the + // MobileViewportManager any more (in this presShell at least). Destroying + // it simplifies things because then it can maintain an invariant that any + // time it gets a meta-viewport change (for example) it knows it must + // recompute the CSS viewport and do a reflow. If we didn't destroy it here + // then there would be times where a meta-viewport change would have no + // effect. + mMobileViewportManager->Destroy(); + mMobileViewportManager = nullptr; + } + return ResizeReflowIgnoreOverride(aWidth, aHeight); } @@ -1763,6 +1783,15 @@ PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight) // didn't ask to ignore the override. Pretend it didn't happen. return NS_OK; } + + if (mMobileViewportManager) { + // If we have a mobile viewport manager, request a reflow from it. It can + // recompute the final CSS viewport and trigger a call to + // ResizeReflowIgnoreOverride if it changed. + mMobileViewportManager->RequestReflow(); + return NS_OK; + } + return ResizeReflowIgnoreOverride(aWidth, aHeight); } diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index 9bcb48f11923..574ff9ea8f58 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -36,6 +36,7 @@ #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "mozilla/MemoryReporting.h" +#include "MobileViewportManager.h" #include "ZoomConstraintsClient.h" class nsRange; @@ -105,6 +106,7 @@ public: virtual nsresult Initialize(nscoord aWidth, nscoord aHeight) override; virtual nsresult ResizeReflow(nscoord aWidth, nscoord aHeight) override; virtual nsresult ResizeReflowOverride(nscoord aWidth, nscoord aHeight) override; + virtual nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight) override; virtual nsIPageSequenceFrame* GetPageSequenceFrame() const override; virtual nsCanvasFrame* GetCanvasFrame() const override; virtual nsIFrame* GetRealPrimaryFrameFor(nsIContent* aContent) const override; @@ -356,7 +358,9 @@ public: virtual nsresult SetIsActive(bool aIsActive) override; - virtual bool GetIsViewportOverridden() override { return mViewportOverridden; } + virtual bool GetIsViewportOverridden() override { + return mViewportOverridden || (mMobileViewportManager != nullptr); + } virtual bool IsLayoutFlushObserver() override { @@ -448,9 +452,6 @@ protected: // sets up. void ScheduleReflow(); - // Reflow regardless of whether the override bit has been set. - nsresult ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight); - // DoReflow returns whether the reflow finished without interruption bool DoReflow(nsIFrame* aFrame, bool aInterruptible); #ifdef DEBUG @@ -818,6 +819,7 @@ protected: TouchManager mTouchManager; nsRefPtr mZoomConstraintsClient; + nsRefPtr mMobileViewportManager; // TouchCaret nsRefPtr mTouchCaret;