Bug 1885101: Match screen and window properties with top window for ScreenRect, ScreenAvailRect and WindowOuterSize. r=timhuang,emilio

This patch removes test_iframe.html. We remove it because the newly introduced test covers the tests done in that test. The reason for removing it in the first place is now that screen properties are inherited/spoofed xorigin, we get a 4px difference. The reasosn for 4px difference is the test runner runs tests in an iframe with a 2px border on each side.

Differential Revision: https://phabricator.services.mozilla.com/D215509
This commit is contained in:
Fatih 2024-08-06 15:31:34 +00:00
parent 94c67a138f
commit de4bc1fb04
12 changed files with 205 additions and 89 deletions

View File

@ -27,8 +27,6 @@ scheme = "https"
scheme = "https"
support-files = ["test_hide_gamepad_info_iframe.html"]
["test_iframe.html"]
["test_keyboard_event.html"]
["test_pointer_event.html"]
@ -36,3 +34,5 @@ support-files = ["../../../../../dom/events/test/pointerevents/mochitest_support
["test_speech_synthesis.html"]
skip-if = ["verify"]
["test_bug1885101_screenwindow_sizes.html"]

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<title>Tests if +WindowOuterSizeExceptIFrame works properly</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
<iframe id="mainFrame"></iframe>
<template id="mainFrameContents">
<script>
window.parent.postMessage(
{
screen: {
height: window.screen.height,
width: window.screen.width,
availHeight: window.screen.availHeight,
availWidth: window.screen.availWidth,
},
outerHeight,
outerWidth,
},
"*"
);
</script>
</template>
<script>
document.addEventListener("DOMContentLoaded", function () {
SimpleTest.waitForExplicitFinish();
window.addEventListener("message", e => {
const data = e.data;
// Check for outer size
SimpleTest.is(
data.outerHeight,
window.outerHeight,
"iframe's window.outerHeight should be equal to window.top.outerHeight"
);
SimpleTest.is(
data.outerWidth,
window.outerWidth,
"iframe's window.outerWidth should be equal to window.top.outerWidth"
);
// Check for screen size
SimpleTest.is(
data.screen.height,
window.screen.height,
"iframe's window.screen.height should be equal to window.top.screen.height"
);
SimpleTest.is(
data.screen.width,
window.screen.width,
"iframe's window.screen.width should be equal to window.top.screen.width"
);
// Check for avail size
SimpleTest.is(
data.screen.availHeight,
window.screen.availHeight,
"iframe's window.screen.availHeight should be equal to window.top.screen.availHeight"
);
SimpleTest.is(
data.screen.availWidth,
window.screen.availWidth,
"iframe's window.screen.availWidth should be equal to window.top.screen.availWidth"
);
SimpleTest.finish();
});
function setFrameSource() {
const frame = document.getElementById("mainFrame");
const template = document.getElementById("mainFrameContents");
frame.srcdoc = template.innerHTML;
}
SpecialPowers.pushPrefEnv(
{
set: [
["privacy.fingerprintingProtection", true],
[
"privacy.fingerprintingProtection.overrides",
"+WindowOuterSize,+ScreenRect,+ScreenAvailRect",
],
],
},
() => setFrameSource()
);
});
</script>
</body>
</html>

View File

@ -1,18 +0,0 @@
<!doctype html>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
<body>
<script>
add_task(async function() {
await SpecialPowers.pushPrefEnv({
"set": [["privacy.resistFingerprinting", true]],
});
is(screen.width, window.innerWidth, "Width should be spoofed");
is(screen.height, window.innerHeight, "Height should be spoofed");
let iframe = document.createElement("iframe");
document.body.appendChild(iframe);
is(iframe.contentWindow.screen.width, iframe.contentWindow.innerWidth, "Width should be spoofed in iframe");
is(iframe.contentWindow.screen.height, iframe.contentWindow.innerHeight, "Height should be spoofed in iframe");
});
</script>
</body>

View File

@ -272,7 +272,10 @@ struct EmbedderColorSchemes {
/* If true, this browsing context is within a hidden embedded document. */ \
FIELD(IsUnderHiddenEmbedderElement, bool) \
/* If true, this browsing context is offline */ \
FIELD(ForceOffline, bool)
FIELD(ForceOffline, bool) \
/* Used to propagate window.top's inner size for RFPTarget::Window* \
* protections */ \
FIELD(TopInnerSizeForRFP, CSSIntSize)
// BrowsingContext, in this context, is the cross process replicated
// environment in which information about documents is stored. In
@ -1253,6 +1256,10 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
bool CanSet(FieldIndex<IDX_ForceOffline>, bool aNewValue,
ContentParent* aSource);
bool CanSet(FieldIndex<IDX_TopInnerSizeForRFP>, bool, ContentParent*) {
return IsTop();
}
bool CanSet(FieldIndex<IDX_EmbeddedInContentDocument>, bool,
ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);

View File

@ -324,6 +324,7 @@ void CanonicalBrowsingContext::ReplacedBy(
txn.SetHasRestoreData(GetHasRestoreData());
txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
txn.SetForceOffline(GetForceOffline());
txn.SetTopInnerSizeForRFP(GetTopInnerSizeForRFP());
// Propagate some settings on BrowsingContext replacement so they're not lost
// on bfcached navigations. These are important for GeckoView (see bug

View File

@ -3515,9 +3515,10 @@ CSSIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
ErrorResult& aError) {
if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
RFPTarget::WindowOuterSize)) {
CSSSize size;
aError = GetInnerSize(size);
return RoundedToInt(size);
if (BrowsingContext* bc = GetBrowsingContext()) {
return bc->Top()->GetTopInnerSizeForRFP();
}
return {};
}
// Windows showing documents in RDM panes and any subframes within them

View File

@ -62,7 +62,7 @@ nsDeviceContext* nsScreen::GetDeviceContext() const {
CSSIntRect nsScreen::GetRect() {
// Return window inner rect to prevent fingerprinting.
if (ShouldResistFingerprinting(RFPTarget::ScreenRect)) {
return GetWindowInnerRect();
return GetTopWindowInnerRectForRFP();
}
// Here we manipulate the value of aRect to represent the screen size,
@ -91,7 +91,7 @@ CSSIntRect nsScreen::GetRect() {
CSSIntRect nsScreen::GetAvailRect() {
// Return window inner rect to prevent fingerprinting.
if (ShouldResistFingerprinting(RFPTarget::ScreenAvailRect)) {
return GetWindowInnerRect();
return GetTopWindowInnerRectForRFP();
}
// Here we manipulate the value of aRect to represent the screen size,
@ -165,18 +165,14 @@ JSObject* nsScreen::WrapObject(JSContext* aCx,
return Screen_Binding::Wrap(aCx, this, aGivenProto);
}
CSSIntRect nsScreen::GetWindowInnerRect() {
nsCOMPtr<nsPIDOMWindowInner> win = GetOwnerWindow();
if (!win) {
return {};
CSSIntRect nsScreen::GetTopWindowInnerRectForRFP() {
if (nsPIDOMWindowInner* inner = GetOwnerWindow()) {
if (BrowsingContext* bc = inner->GetBrowsingContext()) {
CSSIntSize size = bc->Top()->GetTopInnerSizeForRFP();
return {0, 0, size.width, size.height};
}
}
double width;
double height;
if (NS_FAILED(win->GetInnerWidth(&width)) ||
NS_FAILED(win->GetInnerHeight(&height))) {
return {};
}
return {0, 0, int32_t(std::round(width)), int32_t(std::round(height))};
return {};
}
bool nsScreen::ShouldResistFingerprinting(RFPTarget aTarget) const {

View File

@ -88,7 +88,7 @@ class nsScreen : public mozilla::DOMEventTargetHelper {
nsDeviceContext* GetDeviceContext() const;
mozilla::CSSIntRect GetRect();
mozilla::CSSIntRect GetAvailRect();
mozilla::CSSIntRect GetWindowInnerRect();
mozilla::CSSIntRect GetTopWindowInnerRectForRFP();
private:
virtual ~nsScreen();

View File

@ -24,14 +24,14 @@ var test = function (isContent) {
["mozInnerScreenY", 0],
["screen.pixelDepth", 24],
["screen.colorDepth", 24],
["screen.availWidth", "innerWidth"],
["screen.availHeight", "innerHeight"],
["screen.availWidth", "outerWidth"],
["screen.availHeight", "outerHeight"],
["screen.left", 0],
["screen.top", 0],
["screen.availLeft", 0],
["screen.availTop", 0],
["screen.width", "innerWidth"],
["screen.height", "innerHeight"],
["screen.width", "outerWidth"],
["screen.height", "outerHeight"],
["screen.orientation.type", "'landscape-primary'"],
["screen.orientation.angle", 0],
["screen.mozOrientation", "'landscape-primary'"],

View File

@ -1463,6 +1463,32 @@ void nsPresContext::SetOverrideDPPX(float aDPPX) {
MediaFeatureChangePropagation::JustThisDocument);
}
void nsPresContext::UpdateTopInnerSizeForRFP() {
if (!mDocument->ShouldResistFingerprinting(RFPTarget::WindowOuterSize) ||
!mDocument->GetBrowsingContext() ||
!mDocument->GetBrowsingContext()->IsTop()) {
return;
}
CSSSize size = CSSPixel::FromAppUnits(GetVisibleArea().Size());
switch (StaticPrefs::dom_innerSize_rounding()) {
case 1:
size.width = std::roundf(size.width);
size.height = std::roundf(size.height);
break;
case 2:
size.width = std::truncf(size.width);
size.height = std::truncf(size.height);
break;
default:
break;
}
Unused << mDocument->GetBrowsingContext()->SetTopInnerSizeForRFP(
CSSIntSize{(int)size.width, (int)size.height});
}
gfxSize nsPresContext::ScreenSizeInchesForFontInflation(bool* aChanged) {
if (aChanged) {
*aChanged = false;
@ -2964,6 +2990,8 @@ void nsPresContext::SetVisibleArea(const nsRect& aRect) {
{mozilla::MediaFeatureChangeReason::ViewportChange},
MediaFeatureChangePropagation::JustThisDocument);
}
UpdateTopInnerSizeForRFP();
}
}

View File

@ -552,6 +552,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
void SetFullZoom(float aZoom);
void SetOverrideDPPX(float);
void SetInRDMPane(bool aInRDMPane);
void UpdateTopInnerSizeForRFP();
public:
float GetFullZoom() { return mFullZoom; }

View File

@ -34,12 +34,12 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "*",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: true,
firstParty: true,
thirdParty: true,
@ -57,12 +57,12 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.com",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: true,
firstParty: true,
thirdParty: false,
@ -80,13 +80,13 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.com",
thirdPartyDomain: "*",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: true,
firstParty: true,
thirdParty: true,
@ -104,13 +104,13 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.com",
thirdPartyDomain: "example.org",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: false,
firstParty: false,
thirdParty: true,
@ -128,13 +128,13 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "*",
thirdPartyDomain: "example.org",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: false,
firstParty: false,
thirdParty: true,
@ -153,12 +153,12 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.net",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: false,
firstParty: false,
thirdParty: false,
@ -177,13 +177,13 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.net",
thirdPartyDomain: "*",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: false,
firstParty: false,
thirdParty: false,
@ -202,13 +202,13 @@ const TEST_CASES = [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.net",
thirdPartyDomain: "example.com",
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: false,
firstParty: false,
thirdParty: false,
@ -221,13 +221,13 @@ const TEST_CASES = [
},
},
// Test multiple entries that enable HW concurrency in the first-party context
// and WindowOuter in the third-party context.
// and ScreenAvailRect in the third-party context.
{
entires: [
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.com",
},
{
@ -239,7 +239,7 @@ const TEST_CASES = [
},
],
expects: {
windowOuter: {
screenAvailRect: {
top: true,
firstParty: true,
thirdParty: false,
@ -335,22 +335,22 @@ async function openAndSetupTestPageForPopup() {
}
async function verifyResultInTab(tab, firstPartyBC, thirdPartyBC, expected) {
let testWindowOuter = enabled => {
let testScreenAvailRect = enabled => {
if (enabled) {
ok(
content.wrappedJSObject.outerHeight ==
content.wrappedJSObject.innerHeight &&
content.wrappedJSObject.outerWidth ==
content.wrappedJSObject.innerWidth,
"Fingerprinting target WindowOuterSize is enabled for WindowOuterSize."
content.wrappedJSObject.screen.availHeight ==
content.wrappedJSObject.screen.height &&
content.wrappedJSObject.screen.availWidth ==
content.wrappedJSObject.screen.width,
"Fingerprinting target ScreenAvailRect is enabled for ScreenAvailRect."
);
} else {
ok(
content.wrappedJSObject.outerHeight !=
content.wrappedJSObject.innerHeight ||
content.wrappedJSObject.outerWidth !=
content.wrappedJSObject.innerWidth,
"Fingerprinting target WindowOuterSize is not enabled for WindowOuterSize."
content.wrappedJSObject.screen.availHeight !=
content.wrappedJSObject.screen.height ||
content.wrappedJSObject.screen.availWidth !=
content.wrappedJSObject.screen.width,
"Fingerprinting target ScreenAvailRect is not enabled for ScreenAvailRect."
);
}
};
@ -370,8 +370,8 @@ async function verifyResultInTab(tab, firstPartyBC, thirdPartyBC, expected) {
);
await SpecialPowers.spawn(
tab.linkedBrowser,
[expected.windowOuter.top],
testWindowOuter
[expected.screenAvailRect.top],
testScreenAvailRect
);
let expectHWConcurrencyTop = expected.hwConcurrency.top
? SPOOFED_HW_CONCURRENCY
@ -401,8 +401,8 @@ async function verifyResultInTab(tab, firstPartyBC, thirdPartyBC, expected) {
);
await SpecialPowers.spawn(
firstPartyBC,
[expected.windowOuter.firstParty],
testWindowOuter
[expected.screenAvailRect.firstParty],
testScreenAvailRect
);
let expectHWConcurrencyFirstParty = expected.hwConcurrency.firstParty
? SPOOFED_HW_CONCURRENCY
@ -432,8 +432,8 @@ async function verifyResultInTab(tab, firstPartyBC, thirdPartyBC, expected) {
);
await SpecialPowers.spawn(
thirdPartyBC,
[expected.windowOuter.thirdParty],
testWindowOuter
[expected.screenAvailRect.thirdParty],
testScreenAvailRect
);
let expectHWConcurrencyThirdParty = expected.hwConcurrency.thirdParty
? SPOOFED_HW_CONCURRENCY
@ -517,7 +517,7 @@ add_task(async function test_popup_inheritance() {
{
id: "1",
last_modified: 1000000000000001,
overrides: "+WindowOuterSize",
overrides: "+ScreenRect,+ScreenAvailRect",
firstPartyDomain: "example.com",
thirdPartyDomain: "example.org",
},
@ -532,22 +532,22 @@ add_task(async function test_popup_inheritance() {
// Ensure the third-party iframe has the correct overrides.
await SpecialPowers.spawn(thirdPartyFrameBC, [], _ => {
ok(
content.wrappedJSObject.outerHeight ==
content.wrappedJSObject.innerHeight &&
content.wrappedJSObject.outerWidth ==
content.wrappedJSObject.innerWidth,
"Fingerprinting target WindowOuterSize is enabled for third-party iframe."
content.wrappedJSObject.screen.availHeight ==
content.wrappedJSObject.screen.height &&
content.wrappedJSObject.screen.availWidth ==
content.wrappedJSObject.screen.width,
"Fingerprinting target ScreenAvailRect is enabled for third-party iframe."
);
});
// Verify the popup inherits overrides from the opener.
await SpecialPowers.spawn(popupBC, [], _ => {
ok(
content.wrappedJSObject.outerHeight ==
content.wrappedJSObject.innerHeight &&
content.wrappedJSObject.outerWidth ==
content.wrappedJSObject.innerWidth,
"Fingerprinting target WindowOuterSize is enabled for the pop-up."
content.wrappedJSObject.screen.availHeight ==
content.wrappedJSObject.screen.height &&
content.wrappedJSObject.screen.availWidth ==
content.wrappedJSObject.screen.width,
"Fingerprinting target ScreenAvailRect is enabled for the pop-up."
);
content.close();