Bug 1676843 - Make innerWidth/innerHeight return the actual CSS size rather than a rounded version of it. r=heycam

On Nightly, for now. This allows websites to get the real viewport size.

The rounded size has caused problems in the past (see bug 1648298 / bug
1648265), and changing it would be ideal.

I told the CSSWG that we can experiment with this on Nightly to
alleviate (or prove) compat concerns, in which case we'd need to use a
different solution, see https://github.com/w3c/csswg-drafts/issues/5260.

Differential Revision: https://phabricator.services.mozilla.com/D96826
This commit is contained in:
Emilio Cobos Álvarez 2020-11-13 12:44:01 +00:00
parent d9ef0f274b
commit a33ff60406
9 changed files with 134 additions and 71 deletions

View File

@ -3231,11 +3231,11 @@ void nsGlobalWindowInner::SetName(const nsAString& aName,
FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, );
}
int32_t nsGlobalWindowInner::GetInnerWidth(CallerType aCallerType,
ErrorResult& aError) {
double nsGlobalWindowInner::GetInnerWidth(CallerType aCallerType,
ErrorResult& aError) {
// We ignore aCallerType; we only have that argument because some other things
// called by GetReplaceableWindowCoord need it. If this ever changes, fix
// nsresult nsGlobalWindowInner::GetInnerWidth(int32_t* aInnerWidth)
// nsresult nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth)
// to actually take a useful CallerType and pass it in here.
FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
}
@ -3248,7 +3248,7 @@ void nsGlobalWindowInner::GetInnerWidth(JSContext* aCx,
aCallerType, aError);
}
nsresult nsGlobalWindowInner::GetInnerWidth(int32_t* aInnerWidth) {
nsresult nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth) {
ErrorResult rv;
// Callee doesn't care about the caller type, but play it safe.
*aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv);
@ -3256,7 +3256,7 @@ nsresult nsGlobalWindowInner::GetInnerWidth(int32_t* aInnerWidth) {
return rv.StealNSResult();
}
void nsGlobalWindowInner::SetInnerWidth(int32_t aInnerWidth,
void nsGlobalWindowInner::SetInnerWidth(double aInnerWidth,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter,
@ -3271,11 +3271,11 @@ void nsGlobalWindowInner::SetInnerWidth(JSContext* aCx,
"innerWidth", aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetInnerHeight(CallerType aCallerType,
ErrorResult& aError) {
double nsGlobalWindowInner::GetInnerHeight(CallerType aCallerType,
ErrorResult& aError) {
// We ignore aCallerType; we only have that argument because some other things
// called by GetReplaceableWindowCoord need it. If this ever changes, fix
// nsresult nsGlobalWindowInner::GetInnerHeight(int32_t* aInnerWidth)
// nsresult nsGlobalWindowInner::GetInnerHeight(double* aInnerWidth)
// to actually take a useful CallerType and pass it in here.
FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
}
@ -3288,7 +3288,7 @@ void nsGlobalWindowInner::GetInnerHeight(JSContext* aCx,
aCallerType, aError);
}
nsresult nsGlobalWindowInner::GetInnerHeight(int32_t* aInnerHeight) {
nsresult nsGlobalWindowInner::GetInnerHeight(double* aInnerHeight) {
ErrorResult rv;
// Callee doesn't care about the caller type, but play it safe.
*aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv);
@ -3296,7 +3296,7 @@ nsresult nsGlobalWindowInner::GetInnerHeight(int32_t* aInnerHeight) {
return rv.StealNSResult();
}
void nsGlobalWindowInner::SetInnerHeight(int32_t aInnerHeight,
void nsGlobalWindowInner::SetInnerHeight(double aInnerHeight,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter,
@ -7135,18 +7135,20 @@ void nsGlobalWindowInner::RedefineProperty(JSContext* aCx,
}
}
template <typename T>
void nsGlobalWindowInner::GetReplaceableWindowCoord(
JSContext* aCx, nsGlobalWindowInner::WindowCoordGetter aGetter,
JSContext* aCx, nsGlobalWindowInner::WindowCoordGetter<T> aGetter,
JS::MutableHandle<JS::Value> aRetval, CallerType aCallerType,
ErrorResult& aError) {
int32_t coord = (this->*aGetter)(aCallerType, aError);
T coord = (this->*aGetter)(aCallerType, aError);
if (!aError.Failed() && !ToJSValue(aCx, coord, aRetval)) {
aError.Throw(NS_ERROR_FAILURE);
}
}
template <typename T>
void nsGlobalWindowInner::SetReplaceableWindowCoord(
JSContext* aCx, nsGlobalWindowInner::WindowCoordSetter aSetter,
JSContext* aCx, nsGlobalWindowInner::WindowCoordSetter<T> aSetter,
JS::Handle<JS::Value> aValue, const char* aPropName, CallerType aCallerType,
ErrorResult& aError) {
/*
@ -7160,8 +7162,8 @@ void nsGlobalWindowInner::SetReplaceableWindowCoord(
return;
}
int32_t value;
if (!ValueToPrimitive<int32_t, eDefault>(aCx, aValue, aPropName, &value)) {
T value;
if (!ValueToPrimitive<T, eDefault>(aCx, aValue, aPropName, &value)) {
aError.Throw(NS_ERROR_UNEXPECTED);
return;
}
@ -7205,13 +7207,14 @@ void nsGlobalWindowInner::SetReplaceableWindowCoord(
winHeight = NSToIntRound(winHeight / scale);
// Acquire content window size.
CSSIntSize contentSize;
CSSSize contentSize;
outer->GetInnerSize(contentSize);
screenMgr->ScreenForRect(winLeft, winTop, winWidth, winHeight,
getter_AddRefs(screen));
if (screen) {
int32_t roundedValue = std::round(value);
int32_t* targetContentWidth = nullptr;
int32_t* targetContentHeight = nullptr;
int32_t screenWidth = 0;
@ -7234,18 +7237,19 @@ void nsGlobalWindowInner::SetReplaceableWindowCoord(
if (innerWidthSpecified || outerWidthSpecified) {
inputWidth = value;
targetContentWidth = &value;
targetContentWidth = &roundedValue;
targetContentHeight = &unused;
} else if (innerHeightSpecified || outerHeightSpecified) {
inputHeight = value;
targetContentWidth = &unused;
targetContentHeight = &value;
targetContentHeight = &roundedValue;
}
nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
chromeWidth, chromeHeight, screenWidth, screenHeight, inputWidth,
inputHeight, outerWidthSpecified, outerHeightSpecified,
targetContentWidth, targetContentHeight);
value = T(roundedValue);
}
}
}

View File

@ -968,33 +968,36 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
// Implementation guts for our writable IDL attributes that are really
// supposed to be readonly replaceable.
typedef int32_t (nsGlobalWindowInner::*WindowCoordGetter)(
template <typename T>
using WindowCoordGetter = T (nsGlobalWindowInner::*)(
mozilla::dom::CallerType aCallerType, mozilla::ErrorResult&);
typedef void (nsGlobalWindowInner::*WindowCoordSetter)(
int32_t, mozilla::dom::CallerType aCallerType, mozilla::ErrorResult&);
void GetReplaceableWindowCoord(JSContext* aCx, WindowCoordGetter aGetter,
template <typename T>
using WindowCoordSetter = void (nsGlobalWindowInner::*)(
T, mozilla::dom::CallerType aCallerType, mozilla::ErrorResult&);
template <typename T>
void GetReplaceableWindowCoord(JSContext* aCx, WindowCoordGetter<T> aGetter,
JS::MutableHandle<JS::Value> aRetval,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
void SetReplaceableWindowCoord(JSContext* aCx, WindowCoordSetter aSetter,
template <typename T>
void SetReplaceableWindowCoord(JSContext* aCx, WindowCoordSetter<T> aSetter,
JS::Handle<JS::Value> aValue,
const char* aPropName,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
// And the implementations of WindowCoordGetter/WindowCoordSetter.
protected:
int32_t GetInnerWidth(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
nsresult GetInnerWidth(int32_t* aWidth) override;
void SetInnerWidth(int32_t aInnerWidth, mozilla::dom::CallerType aCallerType,
double GetInnerWidth(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
nsresult GetInnerWidth(double* aWidth) override;
void SetInnerWidth(double aInnerWidth, mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
protected:
int32_t GetInnerHeight(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
nsresult GetInnerHeight(int32_t* aHeight) override;
void SetInnerHeight(int32_t aInnerHeight,
mozilla::dom::CallerType aCallerType,
double GetInnerHeight(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
nsresult GetInnerHeight(double* aHeight) override;
void SetInnerHeight(double aInnerHeight, mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
int32_t GetScreenX(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);

View File

@ -3553,7 +3553,7 @@ nsIntSize nsGlobalWindowOuter::CSSToDevIntPixelsForBaseWindow(
static_cast<float>(aCSSSize.height * scale * zoom));
}
nsresult nsGlobalWindowOuter::GetInnerSize(CSSIntSize& aSize) {
nsresult nsGlobalWindowOuter::GetInnerSize(CSSSize& aSize) {
EnsureSizeAndPositionUpToDate();
NS_ENSURE_STATE(mDocShell);
@ -3562,7 +3562,7 @@ nsresult nsGlobalWindowOuter::GetInnerSize(CSSIntSize& aSize) {
PresShell* presShell = mDocShell->GetPresShell();
if (!presContext || !presShell) {
aSize = CSSIntSize(0, 0);
aSize = {};
return NS_OK;
}
@ -3580,22 +3580,27 @@ nsresult nsGlobalWindowOuter::GetInnerSize(CSSIntSize& aSize) {
nsLayoutUtils::ExpandHeightForViewportUnits(presContext, viewportSize);
}
aSize = CSSIntRect::FromAppUnitsRounded(viewportSize);
aSize = CSSPixel::FromAppUnits(viewportSize);
if (StaticPrefs::dom_innerSize_rounded()) {
aSize.width = std::roundf(aSize.width);
aSize.height = std::roundf(aSize.height);
}
return NS_OK;
}
int32_t nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
CSSIntSize size;
double nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
CSSSize size;
aError = GetInnerSize(size);
return size.width;
}
nsresult nsGlobalWindowOuter::GetInnerWidth(int32_t* aInnerWidth) {
nsresult nsGlobalWindowOuter::GetInnerWidth(double* aInnerWidth) {
FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
}
void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
void nsGlobalWindowOuter::SetInnerWidthOuter(double aInnerWidth,
CallerType aCallerType,
ErrorResult& aError) {
if (!mDocShell) {
@ -3603,7 +3608,10 @@ void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
return;
}
CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerType);
// We only allow setting integers, for now.
int32_t value = std::round(ToZeroIfNonfinite(aInnerWidth));
CheckSecurityWidthAndHeight(&value, nullptr, aCallerType);
RefPtr<PresShell> presShell = mDocShell->GetPresShell();
// Setting inner width should set the CSS viewport. If the CSS viewport
@ -3616,8 +3624,7 @@ void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
nsRect shellArea = presContext->GetVisibleArea();
height = shellArea.Height();
SetCSSViewportWidthAndHeight(
nsPresContext::CSSPixelsToAppUnits(aInnerWidth), height);
SetCSSViewportWidthAndHeight(CSSPixel::ToAppUnits(value), height);
return;
}
@ -3628,20 +3635,20 @@ void nsGlobalWindowOuter::SetInnerWidthOuter(int32_t aInnerWidth,
nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
docShellAsWin->GetSize(&unused, &height);
aError = SetDocShellWidthAndHeight(
CSSToDevIntPixelsForBaseWindow(aInnerWidth, docShellAsWin), height);
CSSToDevIntPixelsForBaseWindow(value, docShellAsWin), height);
}
int32_t nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
CSSIntSize size;
double nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
CSSSize size;
aError = GetInnerSize(size);
return size.height;
}
nsresult nsGlobalWindowOuter::GetInnerHeight(int32_t* aInnerHeight) {
nsresult nsGlobalWindowOuter::GetInnerHeight(double* aInnerHeight) {
FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
}
void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
void nsGlobalWindowOuter::SetInnerHeightOuter(double aInnerHeight,
CallerType aCallerType,
ErrorResult& aError) {
if (!mDocShell) {
@ -3649,7 +3656,8 @@ void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
return;
}
CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerType);
int32_t value = std::round(ToZeroIfNonfinite(aInnerHeight));
CheckSecurityWidthAndHeight(nullptr, &value, aCallerType);
RefPtr<PresShell> presShell = mDocShell->GetPresShell();
// Setting inner height should set the CSS viewport. If the CSS viewport
@ -3662,8 +3670,7 @@ void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
nsRect shellArea = presContext->GetVisibleArea();
width = shellArea.Width();
SetCSSViewportWidthAndHeight(
width, nsPresContext::CSSPixelsToAppUnits(aInnerHeight));
SetCSSViewportWidthAndHeight(width, CSSPixel::ToAppUnits(value));
return;
}
@ -3674,15 +3681,15 @@ void nsGlobalWindowOuter::SetInnerHeightOuter(int32_t aInnerHeight,
nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
docShellAsWin->GetSize(&width, &height);
aError = SetDocShellWidthAndHeight(
width, CSSToDevIntPixelsForBaseWindow(aInnerHeight, docShellAsWin));
width, CSSToDevIntPixelsForBaseWindow(value, docShellAsWin));
}
nsIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
ErrorResult& aError) {
if (nsContentUtils::ResistFingerprinting(aCallerType)) {
CSSIntSize size;
CSSSize size;
aError = GetInnerSize(size);
return nsIntSize(size.width, size.height);
return nsIntSize::Round(size.width, size.height);
}
// Windows showing documents in RDM panes and any subframes within them
@ -5435,10 +5442,10 @@ void nsGlobalWindowOuter::MoveToOuter(int32_t aXPos, int32_t aYPos,
do_GetService("@mozilla.org/gfx/screenmanager;1");
nsCOMPtr<nsIScreen> screen;
if (screenMgr) {
CSSIntSize size;
CSSSize size;
GetInnerSize(size);
screenMgr->ScreenForRect(aXPos, aYPos, size.width, size.height,
getter_AddRefs(screen));
screenMgr->ScreenForRect(aXPos, aYPos, std::round(size.width),
std::round(size.height), getter_AddRefs(screen));
}
if (screen) {

View File

@ -685,20 +685,20 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
}
public:
int32_t GetInnerWidthOuter(mozilla::ErrorResult& aError);
double GetInnerWidthOuter(mozilla::ErrorResult& aError);
protected:
nsresult GetInnerWidth(int32_t* aWidth) override;
void SetInnerWidthOuter(int32_t aInnerWidth,
nsresult GetInnerWidth(double* aWidth) override;
void SetInnerWidthOuter(double aInnerWidth,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
public:
int32_t GetInnerHeightOuter(mozilla::ErrorResult& aError);
double GetInnerHeightOuter(mozilla::ErrorResult& aError);
protected:
nsresult GetInnerHeight(int32_t* aHeight) override;
void SetInnerHeightOuter(int32_t aInnerHeight,
nsresult GetInnerHeight(double* aHeight) override;
void SetInnerHeightOuter(double aInnerHeight,
mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
int32_t GetScreenXOuter(mozilla::dom::CallerType aCallerType,
@ -852,7 +852,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
int32_t GetScrollBoundaryOuter(mozilla::Side aSide);
// Outer windows only.
nsresult GetInnerSize(mozilla::CSSIntSize& aSize);
nsresult GetInnerSize(mozilla::CSSSize& aSize);
nsIntSize GetOuterSize(mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aError);
void SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,

View File

@ -557,8 +557,8 @@ class nsPIDOMWindowInner : public mozIDOMWindow {
virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
virtual nsresult GetInnerHeight(int32_t* aHeight) = 0;
virtual nsresult GetInnerWidth(double* aWidth) = 0;
virtual nsresult GetInnerHeight(double* aHeight) = 0;
virtual already_AddRefed<nsICSSDeclaration> GetComputedStyle(
mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
@ -1036,8 +1036,8 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
nsISupports* aExtraArgument,
mozilla::dom::BrowsingContext** _retval) = 0;
virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
virtual nsresult GetInnerHeight(int32_t* aHeight) = 0;
virtual nsresult GetInnerWidth(double* aWidth) = 0;
virtual nsresult GetInnerHeight(double* aHeight) = 0;
virtual mozilla::dom::Element* GetFrameElement() = 0;

View File

@ -297,9 +297,16 @@ nsresult nsScreen::GetWindowInnerRect(nsRect& aRect) {
if (!win) {
return NS_ERROR_FAILURE;
}
nsresult rv = win->GetInnerWidth(&aRect.width);
double width;
double height;
nsresult rv = win->GetInnerWidth(&width);
NS_ENSURE_SUCCESS(rv, rv);
return win->GetInnerHeight(&aRect.height);
rv = win->GetInnerHeight(&height);
NS_ENSURE_SUCCESS(rv, rv);
// FIXME(emilio): This is an nsRect but should really be a CSSIntRect, these
// are CSS pixels!
aRect.SizeTo(std::round(width), std::round(height));
return NS_OK;
}
bool nsScreen::ShouldResistFingerprinting() const {

View File

@ -42,6 +42,7 @@ function test() {
SimpleTest.waitForFocus(function() {
w.onresize = function() {
info(`got resize: ${w.innerWidth} ${w.innerHeight}`);
if (w.innerWidth > 300 - epsilon || isExecuted) {
return;

View File

@ -1901,6 +1901,12 @@
value: @IS_EARLY_BETA_OR_EARLIER@
mirror: always
# Whether innerWidth / innerHeight return rounded or fractional sizes.
- name: dom.innerSize.rounded
type: bool
value: @IS_NOT_NIGHTLY_BUILD@
mirror: always
# Whether we conform to Input Events Level 1 or Input Events Level 2.
# true: conforming to Level 1
# false: conforming to Level 2

View File

@ -0,0 +1,35 @@
<!doctype html>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<pre>
<script>
test(function() {
let originalWidth = window.innerWidth;
let originalHeight = window.innerHeight;
assert_equals(window.devicePixelRatio, 1, `precondition: ${originalWidth}x${originalHeight}`);
// This precondition holds because of:
// https://searchfox.org/mozilla-central/rev/50215d649d4854812837f1343e8f47bd998dacb5/browser/base/content/browser.js#1717
//
// But if this test starts failing you can just update the assert and the
// factor below accordingly so that the asserts keep passing.
assert_equals(originalWidth, 1280, "precondition");
// Set a fractional scale factor that guarantees that we get a fractional innerWidth
const kFactor = 1.5;
const kOneAppUnit = 1 / 60;
SpecialPowers.setFullZoom(window, kFactor);
assert_approx_equals(window.devicePixelRatio, kFactor, kOneAppUnit);
assert_not_equals(window.innerWidth, originalWidth);
assert_not_equals(window.innerHeight, originalHeight);
if (SpecialPowers.getBoolPref("dom.innerSize.rounded")) {
assert_equals(window.innerWidth, Math.round(originalWidth / kFactor));
assert_equals(window.innerHeight, Math.round(originalHeight / kFactor));
} else {
assert_not_equals(window.innerWidth, Math.round(window.innerWidth));
}
SpecialPowers.setFullZoom(window, 1); // Restore zoom so results can be seen fine...
});
</script>