diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index c559d66c876..dc5a19e6a13 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 033982431bb..30f4efd31e5 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 00000000000..f36dde556d4 --- /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 00000000000..26676c602a4 --- /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 ff5755fb31e..7e8797f2e62 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 7040c5e08c7..6580620bdb0 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 0c577eddc2b..f8864178f68 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 9bcb48f1192..574ff9ea8f5 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;