Skip to content

Commit 5cc1a79

Browse files
FKLCfkilic@mozilla.com
authored andcommitted
Bug 1975753 - Force site specific zoom when RFPTarget::SiteSpecificZoom is active. r=timhuang,tabbrowser-reviewers,dao
I wasn't around when RFPTarget::SiteSpecificZoom was implemented, but the current implementation is very confusing. In Firefox, we have two modes of zoom level. Per tab zoom, and site specific zoom. It is controlled in two (maybe more) places. browser-fullZoom.js and in [CanonicalBrowsingContext.cpp](https://searchfox.org/mozilla-central/rev/ac81a39dfe0663eb40a34c7c36d89e34be29cb20/docshell/base/CanonicalBrowsingContext.cpp#285-289). Our current implementation disables site specific zoom in `browser-fullZoom.js` by checking the RFP target, but it doesn't modify `CanonicalBrowsingContext.cpp`. So,`CanonicalBrowsingContext` still thinks we are using site specific zoom. Pre-bug1914149 this method worked because we didn't keep/inherit zoom level across navigations. Post-bug1914149, it no longer works because we keep the zoom level across navigations in `CanonicalBrowsingContext` and let `browser-fullZoom.js` reset to its correct value back. The issue is caused because `CanonicalBrowsingContext` keeps the previous page's zoom level, but `browser-fullZoom.js` thinks we use tab zoom mode, so it doesn't bother setting the zoom level for the site/page. So, we end up keeping the zoom level. The solution here I'm suggesting is, doing the opposite of what we are doing in `browser-fullZoom.js`. So, now we should force SiteSpecificZoom with RFP. The reason I'm suggesting this is because within the same site, even acrss tabs, we persist cookies, so fingerprinting isn't much of concern here. We also don't persist zoom levels in private browsing. So, linking normal to PBM isn't a concern either. So, in summary, - If you open the same site, in 100 tabs, all of them will get the same zoom level with this patch (just like default normal Firefox) - If you are in PBM, the zoom level is NOT persisted. - If you are in normal browsing, the zoom level is persisted, but so are cookies. Differential Revision: https://phabricator.services.mozilla.com/D257497
1 parent ad4e9c6 commit 5cc1a79

File tree

5 files changed

+146
-100
lines changed

5 files changed

+146
-100
lines changed

browser/components/resistfingerprinting/test/browser/browser.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ support-files = [
5858

5959
["browser_block_mozAddonManager.js"]
6060

61-
["browser_bug1369357_site_specific_zoom_level.js"]
61+
["browser_bug1975753_site_specific_zoom_level.js"]
6262
https_first_disabled = true
6363

6464
["browser_canvas_iframes.js"]

browser/components/resistfingerprinting/test/browser/browser_bug1369357_site_specific_zoom_level.js

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use strict";
2+
3+
const PATH_NET = TEST_PATH + "file_dummy.html";
4+
const PATH_ORG = PATH_NET.replace("example.net", "example.org");
5+
6+
add_task(async function () {
7+
let tab1, tab1Zoom;
8+
9+
tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
10+
await FullZoom.setZoom(1.25, tab1.linkedBrowser);
11+
tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
12+
13+
await new Promise(resolve => {
14+
Services.clearData.deleteDataFromHost(
15+
PATH_NET,
16+
true /* user request */,
17+
Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE,
18+
_ => {
19+
resolve();
20+
}
21+
);
22+
});
23+
24+
is(
25+
tab1Zoom,
26+
1.25,
27+
`privacy.resistFingerprinting is false, site-specific zoom should not be reset when clearing FPP state`
28+
);
29+
30+
await SpecialPowers.pushPrefEnv({
31+
set: [["privacy.resistFingerprinting", true]],
32+
});
33+
34+
await new Promise(resolve => {
35+
Services.clearData.deleteDataFromHost(
36+
PATH_NET,
37+
true /* user request */,
38+
Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE,
39+
_ => {
40+
resolve();
41+
}
42+
);
43+
});
44+
45+
tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
46+
47+
is(
48+
tab1Zoom,
49+
1.0,
50+
"privacy.resistFingerprinting is true, site-specific zoom should be reset when clearing FPP state for tab1"
51+
);
52+
53+
await FullZoom.reset();
54+
55+
BrowserTestUtils.removeTab(tab1);
56+
57+
await SpecialPowers.popPrefEnv();
58+
});

browser/components/tabbrowser/content/browser-fullZoom.js

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ var FullZoom = {
236236
// to the new location.
237237
this._ignorePendingZoomAccesses(browser);
238238

239-
if (!aURI || (aIsTabSwitch && !this._isSiteSpecific(browser))) {
239+
if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
240240
this._notifyOnLocationChange(browser);
241241
return;
242242
}
@@ -530,13 +530,13 @@ var FullZoom = {
530530
if (
531531
!aBrowser.mInitialized ||
532532
aBrowser.isSyntheticDocument ||
533-
(!this._isSiteSpecific(aBrowser) && aBrowser.tabHasCustomZoom)
533+
(!this.siteSpecific && aBrowser.tabHasCustomZoom)
534534
) {
535535
this._executeSoon(aCallback);
536536
return;
537537
}
538538

539-
if (aValue !== undefined && this._isSiteSpecific(aBrowser)) {
539+
if (aValue !== undefined && this.siteSpecific) {
540540
ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
541541
this._ignorePendingZoomAccesses(aBrowser);
542542
this._executeSoon(aCallback);
@@ -563,11 +563,11 @@ var FullZoom = {
563563
* @param browser The zoom of this browser will be saved. Required.
564564
*/
565565
_applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
566-
if (!this._isSiteSpecific(browser) || browser.isSyntheticDocument) {
566+
if (!this.siteSpecific || browser.isSyntheticDocument) {
567567
// If site-specific zoom is disabled, we have called this function
568568
// to adjust our tab's zoom level. It is now considered "custom"
569569
// and we mark that here.
570-
browser.tabHasCustomZoom = !this._isSiteSpecific(browser);
570+
browser.tabHasCustomZoom = !this.siteSpecific;
571571
return null;
572572
}
573573

@@ -697,23 +697,6 @@ var FullZoom = {
697697
return aValue;
698698
},
699699

700-
// Whether to remember the site specific zoom level for this browser.
701-
// This returns false when `browser.zoom.siteSpecific` is false or
702-
// the browser has content loaded that should resist fingerprinting.
703-
_isSiteSpecific(aBrowser) {
704-
if (!this.siteSpecific) {
705-
return false;
706-
}
707-
return (
708-
!aBrowser?.browsingContext?.topWindowContext.shouldResistFingerprinting ||
709-
!ChromeUtils.shouldResistFingerprinting(
710-
"SiteSpecificZoom",
711-
aBrowser?.browsingContext?.topWindowContext
712-
.overriddenFingerprintingSettings
713-
)
714-
);
715-
},
716-
717700
/**
718701
* Gets the load context from the given Browser.
719702
*

toolkit/components/cleardata/ClearDataService.sys.mjs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,32 +416,114 @@ const CookieBannerExecutedRecordCleaner = {
416416

417417
// A cleaner for cleaning fingerprinting protection states.
418418
const FingerprintingProtectionStateCleaner = {
419+
async _maybeClearSiteSpecificZoom(
420+
deleteAll,
421+
aSchemelessSite,
422+
aOriginAttributes = {}
423+
) {
424+
if (
425+
!ChromeUtils.shouldResistFingerprinting("SiteSpecificZoom", null, true)
426+
) {
427+
return;
428+
}
429+
430+
const cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
431+
Ci.nsIContentPrefService2
432+
);
433+
const ZOOM_PREF_NAME = "browser.content.full-zoom";
434+
435+
await new Promise((aResolve, aReject) => {
436+
if (deleteAll) {
437+
cps2.removeByName(ZOOM_PREF_NAME, null, {
438+
handleCompletion: aReason => {
439+
if (aReason === cps2.COMPLETE_ERROR) {
440+
aReject();
441+
} else {
442+
aResolve();
443+
}
444+
},
445+
});
446+
} else {
447+
aOriginAttributes =
448+
ChromeUtils.fillNonDefaultOriginAttributes(aOriginAttributes);
449+
450+
let loadContext;
451+
if (
452+
aOriginAttributes.privateBrowsingId ==
453+
Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID
454+
) {
455+
loadContext = Cu.createLoadContext();
456+
} else {
457+
loadContext = Cu.createPrivateLoadContext();
458+
}
459+
460+
cps2.removeBySubdomainAndName(
461+
aSchemelessSite,
462+
ZOOM_PREF_NAME,
463+
loadContext,
464+
{
465+
handleCompletion: aReason => {
466+
if (aReason === cps2.COMPLETE_ERROR) {
467+
aReject();
468+
} else {
469+
aResolve();
470+
}
471+
},
472+
}
473+
);
474+
}
475+
});
476+
},
477+
419478
async deleteAll() {
420479
Services.rfp.cleanAllRandomKeys();
480+
481+
await this._maybeClearSiteSpecificZoom(true);
421482
},
422483

423484
async deleteByPrincipal(aPrincipal) {
424485
Services.rfp.cleanRandomKeyByPrincipal(aPrincipal);
486+
487+
await this._maybeClearSiteSpecificZoom(
488+
false,
489+
aPrincipal.host,
490+
aPrincipal.originAttributes
491+
);
425492
},
426493

427494
async deleteBySite(aSchemelessSite, aOriginAttributesPattern) {
428495
Services.rfp.cleanRandomKeyBySite(
429496
aSchemelessSite,
430497
aOriginAttributesPattern
431498
);
499+
500+
await this._maybeClearSiteSpecificZoom(
501+
false,
502+
aSchemelessSite,
503+
aOriginAttributesPattern
504+
);
432505
},
433506

434507
async deleteByHost(aHost, aOriginAttributesPattern) {
435508
Services.rfp.cleanRandomKeyByHost(
436509
aHost,
437510
JSON.stringify(aOriginAttributesPattern)
438511
);
512+
513+
await this._maybeClearSiteSpecificZoom(
514+
false,
515+
aHost,
516+
aOriginAttributesPattern
517+
);
439518
},
440519

441520
async deleteByOriginAttributes(aOriginAttributesString) {
442521
Services.rfp.cleanRandomKeyByOriginAttributesPattern(
443522
aOriginAttributesString
444523
);
524+
525+
// For deleteByOriginAttributes, we only receive userContextId which is not enough to target specific
526+
// site-specific zooms. So we don't clear site-specific zooms here.
445527
},
446528
};
447529

0 commit comments

Comments
 (0)