Bug 1125325 - Make TabParent/TabChild UpdateDimensions messages aware of the display scale. r=kats

When connecting a lowdpi external monitor on hidpi mac, TabChild gets an
UpdateDimensions call, followed by a UIResolutionChanged call.  After
the UpdateDimensions call, the content process is in an incorrect state
where it has the dimensions of the new display and scale of the old one.
After the UIResolutionChanged message, the values are again consistent.
In the interim, reflow resizes layers based on the incorrect (old) scale
and subsequently uses those incorrect values when the new scale comes
in.  This patch normalizes the message parameters by dividing by scale
(the result is what OS X calls point coordinates) so that this doesn't happen.
This commit is contained in:
David Parks 2015-06-07 22:39:28 -07:00
parent aa01c9241e
commit ef7528b1f2
7 changed files with 121 additions and 40 deletions

View File

@ -25,6 +25,7 @@ using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
using struct gfxSize from "gfxPoint.h";
using CSSRect from "Units.h";
using CSSSize from "Units.h";
using LayoutDeviceIntRect from "Units.h";
using mozilla::LayoutDeviceIntPoint from "Units.h";
using ScreenIntSize from "Units.h";
@ -548,7 +549,7 @@ child:
CacheFileDescriptor(nsString path, FileDescriptor fd);
UpdateDimensions(IntRect rect, ScreenIntSize size, ScreenOrientation orientation,
UpdateDimensions(CSSRect rect, CSSSize size, ScreenOrientation orientation,
LayoutDeviceIntPoint chromeDisp) compressall;
UpdateFrame(FrameMetrics frame);

View File

@ -92,6 +92,7 @@
#include "nsIPermissionManager.h"
#include "nsIScriptError.h"
#include "mozilla/EventForwards.h"
#include "nsDeviceContext.h"
#define BROWSER_ELEMENT_CHILD_SCRIPT \
NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js")
@ -165,7 +166,6 @@ NS_IMPL_ISUPPORTS(TabChild::DelayedFireContextMenuEvent,
TabChildBase::TabChildBase()
: mContentDocumentIsDisplayed(false)
, mTabChildGlobal(nullptr)
, mInnerSize(0, 0)
{
mozilla::HoldJSObjects(this);
}
@ -236,9 +236,11 @@ TabChildBase::InitializeRootMetrics()
mLastRootMetrics.SetViewport(CSSRect(CSSPoint(), kDefaultViewportSize));
mLastRootMetrics.SetCompositionBounds(ParentLayerRect(
ParentLayerPoint(),
ParentLayerSize(ViewAs<ParentLayerPixel>(mInnerSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
ParentLayerSize(
ViewAs<ParentLayerPixel>(GetInnerSize(),
PixelCastJustification::ScreenIsParentLayerForRoot))));
mLastRootMetrics.SetZoom(CSSToParentLayerScale2D(
ConvertScaleForRoot(CalculateIntrinsicScale(mInnerSize, kDefaultViewportSize))));
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.
@ -299,14 +301,14 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
}
TABC_LOG("HandlePossibleViewportChange aOldScreenSize=%s mInnerSize=%s\n",
Stringify(aOldScreenSize).c_str(), Stringify(mInnerSize).c_str());
Stringify(aOldScreenSize).c_str(), Stringify(GetInnerSize()).c_str());
nsCOMPtr<nsIDocument> document(GetDocument());
if (!document) {
return false;
}
nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, mInnerSize);
nsViewportInfo viewportInfo = nsContentUtils::GetViewportInfo(document, GetInnerSize());
uint32_t presShellId = 0;
mozilla::layers::FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
@ -323,8 +325,8 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
constraints);
}
float screenW = mInnerSize.width;
float screenH = mInnerSize.height;
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
@ -358,15 +360,15 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
ScreenIntSize oldScreenSize = aOldScreenSize;
if (oldScreenSize == ScreenIntSize()) {
oldScreenSize = mInnerSize;
oldScreenSize = GetInnerSize();
}
FrameMetrics metrics(mLastRootMetrics);
metrics.SetViewport(CSSRect(CSSPoint(), viewport));
// Calculate the composition bounds based on mInnerSize, excluding the sizes
// Calculate the composition bounds based on the inner size, excluding the sizes
// of the scrollbars if they are not overlay scrollbars.
ScreenSize compositionSize(mInnerSize);
ScreenSize compositionSize(GetInnerSize());
nsCOMPtr<nsIPresShell> shell = GetPresShell();
if (shell) {
nsMargin scrollbarsAppUnits =
@ -380,7 +382,9 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
metrics.SetCompositionBounds(ParentLayerRect(
ParentLayerPoint(),
ParentLayerSize(ViewAs<ParentLayerPixel>(compositionSize, PixelCastJustification::ScreenIsParentLayerForRoot))));
ParentLayerSize(
ViewAs<ParentLayerPixel>(GetInnerSize(),
PixelCastJustification::ScreenIsParentLayerForRoot))));
metrics.SetRootCompositionSize(
ScreenSize(compositionSize) * ScreenToLayoutDeviceScale(1.0f) / metrics.GetDevPixelsPerCSSPixel());
@ -397,7 +401,7 @@ TabChildBase::HandlePossibleViewportChange(const ScreenIntSize& aOldScreenSize)
// 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(mInnerSize, viewport);
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
@ -881,7 +885,6 @@ TabChild::TabChild(nsIContentChild* aManager,
, mManager(aManager)
, mChromeFlags(aChromeFlags)
, mLayersId(0)
, mOuterRect(0, 0, 0, 0)
, mActivePointerId(-1)
, mAppPackageFileDescriptorRecved(false)
, mLastBackgroundColor(NS_RGB(255, 255, 255))
@ -922,9 +925,9 @@ TabChild::HandleEvent(nsIDOMEvent* aEvent)
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(mInnerSize);
HandlePossibleViewportChange(GetInnerSize());
} else if (eventType.EqualsLiteral("FullZoomChange")) {
HandlePossibleViewportChange(mInnerSize);
HandlePossibleViewportChange(GetInnerSize());
}
return NS_OK;
@ -966,14 +969,14 @@ TabChild::Observe(nsISupports *aSubject,
// In some cases before-first-paint gets called before
// RecvUpdateDimensions is called and therefore before we have an
// mInnerSize value set. In such cases defer initializing the viewport
// 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(mInnerSize);
HandlePossibleViewportChange(GetInnerSize());
}
}
}
@ -1313,17 +1316,18 @@ NS_IMETHODIMP
TabChild::GetDimensions(uint32_t aFlags, int32_t* aX,
int32_t* aY, int32_t* aCx, int32_t* aCy)
{
ScreenIntRect rect = GetOuterRect();
if (aX) {
*aX = mOuterRect.x;
*aX = rect.x;
}
if (aY) {
*aY = mOuterRect.y;
*aY = rect.y;
}
if (aCx) {
*aCx = mOuterRect.width;
*aCx = rect.width;
}
if (aCy) {
*aCy = mOuterRect.height;
*aCy = rect.height;
}
return NS_OK;
@ -2050,37 +2054,41 @@ TabChild::RecvShow(const ScreenIntSize& aSize,
}
bool
TabChild::RecvUpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size,
const ScreenOrientation& orientation, const LayoutDeviceIntPoint& chromeDisp)
TabChild::RecvUpdateDimensions(const CSSRect& rect, const CSSSize& size,
const ScreenOrientation& orientation,
const LayoutDeviceIntPoint& chromeDisp)
{
if (!mRemoteFrame) {
return true;
}
mOuterRect = rect;
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) {
mHasValidInnerSize = true;
} else if (mInnerSize == size) {
} else if (screenSize == oldScreenSize) {
sizeChanged = false;
}
mOrientation = orientation;
ScreenIntSize oldScreenSize = mInnerSize;
mInnerSize = size;
mWidget->Resize(rect.x + chromeDisp.x, rect.y + chromeDisp.y, size.width, size.height,
true);
ScreenIntRect screenRect = GetOuterRect();
mWidget->Resize(screenRect.x + chromeDisp.x, screenRect.y + chromeDisp.y,
screenSize.width, screenSize.height, true);
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
baseWin->SetPositionAndSize(0, 0, size.width, size.height,
baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
true);
if (initialSizing && mContentDocumentIsDisplayed) {
// If this is the first time we're getting a valid mInnerSize, and the
// 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.
@ -3244,6 +3252,7 @@ TabChild::RecvRequestNotifyAfterRemotePaint()
bool
TabChild::RecvUIResolutionChanged()
{
ScreenIntSize oldScreenSize = GetInnerSize();
mDPI = 0;
mDefaultScale = 0;
static_cast<PuppetWidget*>(mWidget.get())->ClearBackingScaleCache();
@ -3252,9 +3261,21 @@ TabChild::RecvUIResolutionChanged()
if (presShell) {
nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
if (presContext) {
presContext->UIResolutionChanged();
presContext->UIResolutionChangedSync();
}
}
ScreenIntSize screenSize = GetInnerSize();
if (mHasValidInnerSize && oldScreenSize != screenSize) {
ScreenIntRect screenRect = GetOuterRect();
mWidget->Resize(screenRect.x + mChromeDisp.x, screenRect.y + mChromeDisp.y,
screenSize.width, screenSize.height, true);
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height,
true);
}
return true;
}
@ -3316,6 +3337,22 @@ TabChild::CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut)
return rv;
}
ScreenIntSize
TabChild::GetInnerSize()
{
LayoutDeviceIntSize innerSize =
RoundedToInt(mUnscaledInnerSize * mWidget->GetDefaultScale());
return ViewAs<ScreenPixel>(innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
};
ScreenIntRect
TabChild::GetOuterRect()
{
LayoutDeviceIntRect outerRect =
RoundedToInt(mUnscaledOuterRect * mWidget->GetDefaultScale());
return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
}
TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
: mTabChild(aTabChild)
{

View File

@ -189,6 +189,8 @@ public:
const bool& aIsRoot,
const mozilla::layers::ZoomConstraints& aConstraints) = 0;
virtual ScreenIntSize GetInnerSize() = 0;
protected:
virtual ~TabChildBase();
CSSSize GetPageSize(nsCOMPtr<nsIDocument> aDocument, const CSSSize& aViewport);
@ -222,7 +224,6 @@ protected:
CSSSize mOldViewportSize;
bool mContentDocumentIsDisplayed;
nsRefPtr<TabChildGlobal> mTabChildGlobal;
ScreenIntSize mInnerSize;
mozilla::layers::FrameMetrics mLastRootMetrics;
nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
};
@ -319,8 +320,8 @@ public:
const uint64_t& aLayersId,
PRenderFrameChild* aRenderFrame,
const bool& aParentIsActive) override;
virtual bool RecvUpdateDimensions(const nsIntRect& rect,
const ScreenIntSize& size,
virtual bool RecvUpdateDimensions(const CSSRect& rect,
const CSSSize& size,
const ScreenOrientation& orientation,
const LayoutDeviceIntPoint& chromeDisp) override;
virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) override;
@ -510,6 +511,8 @@ public:
}
bool AsyncPanZoomEnabled() { return mAsyncPanZoomEnabled; }
virtual ScreenIntSize GetInnerSize();
protected:
virtual ~TabChild();
@ -599,6 +602,12 @@ private:
void SetTabId(const TabId& aTabId);
ScreenIntRect GetOuterRect();
void SetUnscaledInnerSize(const CSSSize& aSize) {
mUnscaledInnerSize = aSize;
}
class CachedFileDescriptorInfo;
class CachedFileDescriptorCallbackRunnable;
class DelayedDeleteRunnable;
@ -611,7 +620,7 @@ private:
nsRefPtr<nsIContentChild> mManager;
uint32_t mChromeFlags;
uint64_t mLayersId;
nsIntRect mOuterRect;
CSSRect mUnscaledOuterRect;
// When we're tracking a possible tap gesture, this is the "down"
// point of the touchstart.
LayoutDevicePoint mGestureDownPoint;
@ -646,6 +655,7 @@ private:
bool mIPCOpen;
bool mParentIsActive;
bool mAsyncPanZoomEnabled;
CSSSize mUnscaledInnerSize;
DISALLOW_EVIL_CONSTRUCTORS(TabChild);
};

View File

@ -90,6 +90,7 @@
#include "nsPIWindowRoot.h"
#include "gfxDrawable.h"
#include "ImageOps.h"
#include "UnitTransforms.h"
#include <algorithm>
using namespace mozilla::dom;
@ -966,7 +967,21 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const ScreenIntSize& size)
mOrientation = orientation;
mChromeOffset = chromeOffset;
unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, mChromeOffset);
CSSToLayoutDeviceScale widgetScale;
if (widget) {
widgetScale = widget->GetDefaultScale();
}
LayoutDeviceIntRect devicePixelRect =
ViewAs<LayoutDevicePixel>(mRect,
PixelCastJustification::LayoutDeviceIsScreenForTabDims);
LayoutDeviceIntSize devicePixelSize =
ViewAs<LayoutDevicePixel>(mDimensions.ToUnknownSize(),
PixelCastJustification::LayoutDeviceIsScreenForTabDims);
CSSRect unscaledRect = devicePixelRect / widgetScale;
CSSSize unscaledSize = devicePixelSize / widgetScale;
unused << SendUpdateDimensions(unscaledRect, unscaledSize, orientation, chromeOffset);
}
}

View File

@ -37,7 +37,10 @@ enum class PixelCastJustification : uint8_t {
// technically in screen pixels, as it has not yet accounted for any
// asynchronous transforms. This justification is for viewing the initial
// reference point as a screen point.
LayoutDeviceToScreenForUntransformedEvent
LayoutDeviceToScreenForUntransformedEvent,
// Similar to LayoutDeviceToScreenForUntransformedEvent, PBrowser handles
// some widget/tab dimension information as the OS does -- in screen units.
LayoutDeviceIsScreenForTabDims
};
template <class TargetUnits, class SourceUnits>

View File

@ -1798,6 +1798,15 @@ nsPresContext::UIResolutionChanged()
}
}
void
nsPresContext::UIResolutionChangedSync()
{
if (!mPendingUIResolutionChanged) {
mPendingUIResolutionChanged = true;
UIResolutionChangedInternal();
}
}
/*static*/ bool
nsPresContext::UIResolutionChangedSubdocumentCallback(nsIDocument* aDocument,
void* aData)

View File

@ -791,9 +791,15 @@ public:
* Notify the pres context that the resolution of the user interface has
* changed. This happens if a window is moved between HiDPI and non-HiDPI
* displays, so that the ratio of points to device pixels changes.
* The notification happens asynchronously.
*/
void UIResolutionChanged();
/*
* Like UIResolutionChanged() but invalidates values immediately.
*/
void UIResolutionChangedSync();
/*
* Notify the pres context that a system color has changed
*/