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.
This commit is contained in:
Kartikaya Gupta 2015-07-21 10:51:55 -04:00
parent e8cc477114
commit c10e6a2eda
8 changed files with 401 additions and 472 deletions

View File

@ -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<ScreenPixel>(aScale, PixelCastJustification::ScreenIsParentLayerForRoot);
}
CSSToParentLayerScale ConvertScaleForRoot(CSSToScreenScale aScale)
{
return ViewTargetAs<ParentLayerPixel>(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<ParentLayerPixel>(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<nsIPresShell> 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<nsIPresShell> shell = GetPresShell()) {
nsLayoutUtils::SetCSSViewport(shell, aSize);
}
}
}
CSSSize
TabChildBase::GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport)
{
nsCOMPtr<Element> 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<nsIDocument> 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<nsIPresShell> 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<ParentLayerPixel>(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<nsIDOMWindowUtils>
TabChildBase::GetDOMWindowUtils()
{
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(WebNavigation());
nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
return utils.forget();
}
already_AddRefed<nsIDocument>
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<nsIDOMWindow> window;
aWebProgress->GetDOMWindow(getter_AddRefs(window));
if (!window) {
return NS_OK;
}
nsCOMPtr<nsIDOMDocument> progressDoc;
window->GetDocument(getter_AddRefs(progressDoc));
if (!progressDoc) {
return NS_OK;
}
nsCOMPtr<nsIDOMDocument> domDoc;
WebNavigation()->GetDocument(getter_AddRefs(domDoc));
if (!domDoc || !SameCOMIdentity(domDoc, progressDoc)) {
return NS_OK;
}
nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID));
if (!urifixup) {
return NS_OK;
}
nsCOMPtr<nsIURI> 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<nsIWebProgress> 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<nsIBaseWindow> 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<nsPIWindowRoot> 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) {

View File

@ -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<mozilla::layers::ZoomConstraints>& aConstraints) = 0;
@ -197,19 +190,12 @@ public:
protected:
virtual ~TabChildBase();
CSSSize GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport);
// Get the DOMWindowUtils for the top-level window in this tab.
already_AddRefed<nsIDOMWindowUtils> GetDOMWindowUtils();
// Get the Document for the top-level window in this tab.
already_AddRefed<nsIDocument> GetDocument() const;
// Get the pres-shell of the document for the top-level window in this tab.
already_AddRefed<nsIPresShell> 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<TabChildGlobal> mTabChildGlobal;
mozilla::layers::FrameMetrics mLastRootMetrics;
nsCOMPtr<nsIWebBrowserChrome3> 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

View File

@ -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<nsPIDOMWindow> window = mDocument->GetWindow()) {
mEventTarget = window->GetChromeEventHandler();
}
if (mEventTarget) {
mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
mEventTarget->AddEventListener(FULL_ZOOM_CHANGE, this, false);
}
nsCOMPtr<nsIObserverService> 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<nsIObserverService> 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<float>& 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<ParentLayerPixel>(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<ScreenPixel>(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<float> 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<ScreenPixel>(
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));
}

View File

@ -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<float>& 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<nsIDocument> mDocument;
nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
nsCOMPtr<nsIDOMEventTarget> mEventTarget;
bool mIsFirstPaint;
mozilla::LayoutDeviceIntSize mDisplaySize;
mozilla::CSSSize mMobileViewportSize;
};
#endif

View File

@ -112,6 +112,7 @@ UNIFIED_SOURCES += [
'GeometryUtils.cpp',
'LayoutLogging.cpp',
'MaskLayerImageCache.cpp',
'MobileViewportManager.cpp',
'nsBidi.cpp',
'nsBidiPresUtils.cpp',
'nsCaret.cpp',

View File

@ -139,10 +139,10 @@ typedef struct CapturingContentInfo {
mozilla::StaticRefPtr<nsIContent> 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.

View File

@ -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);
}

View File

@ -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<ZoomConstraintsClient> mZoomConstraintsClient;
nsRefPtr<MobileViewportManager> mMobileViewportManager;
// TouchCaret
nsRefPtr<mozilla::TouchCaret> mTouchCaret;