Backed out 5 changesets (bug 1407366) for causing multiple build bustages CLOSED TREE

Backed out changeset 72a8371c210d (bug 1407366)
Backed out changeset 342a7d9308a0 (bug 1407366)
Backed out changeset b5d089dc2653 (bug 1407366)
Backed out changeset dbae69c2a849 (bug 1407366)
Backed out changeset 5da400636334 (bug 1407366)

--HG--
rename : toolkit/components/resistfingerprinting/RFPHelper.jsm => toolkit/components/resistfingerprinting/LanguagePrompt.jsm
This commit is contained in:
arthur.iakab 2019-02-21 02:55:37 +02:00
Родитель ffa3b62355
Коммит 4fc06d4e3c
10 изменённых файлов: 206 добавлений и 834 удалений

Просмотреть файл

@ -42,7 +42,6 @@ FINAL_TARGET_FILES.actors += [
'PageInfoChild.jsm',
'PageStyleChild.jsm',
'PluginChild.jsm',
'RFPHelperChild.jsm',
'SearchTelemetryChild.jsm',
'URIFixupChild.jsm',
'WebRTCChild.jsm',

Просмотреть файл

@ -28,6 +28,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
HomePage: "resource:///modules/HomePage.jsm",
LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
@ -49,7 +50,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
ReaderParent: "resource:///modules/ReaderParent.jsm",
RFPHelper: "resource://gre/modules/RFPHelper.jsm",
SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
Sanitizer: "resource:///modules/Sanitizer.jsm",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
@ -1948,6 +1948,8 @@ var gBrowserInit = {
ToolbarKeyboardNavigator.uninit();
}
LanguagePrompt.uninit();
BrowserSearch.uninit();
// Now either cancel delayedStartup, or clean up the services initialized from

Просмотреть файл

@ -244,16 +244,6 @@ let ACTORS = {
},
},
RFPHelper: {
child: {
module: "resource:///actors/RFPHelperChild.jsm",
group: "browsers",
events: {
"resize": {},
},
},
},
SearchTelemetry: {
child: {
module: "resource:///actors/SearchTelemetryChild.jsm",
@ -425,7 +415,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
RemoteSettings: "resource://services-settings/remote-settings.js",
RFPHelper: "resource://gre/modules/RFPHelper.jsm",
SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
Sanitizer: "resource:///modules/Sanitizer.jsm",
SaveToPocket: "chrome://pocket/content/SaveToPocket.jsm",
@ -1472,7 +1461,6 @@ BrowserGlue.prototype = {
DateTimePickerParent.uninit();
Normandy.uninit();
RFPHelper.uninit();
},
// Set up a listener to enable/disable the screenshots extension
@ -1686,7 +1674,7 @@ BrowserGlue.prototype = {
}
Services.tm.idleDispatchToMainThread(() => {
RFPHelper.init();
LanguagePrompt.init();
});
Services.tm.idleDispatchToMainThread(() => {

Просмотреть файл

@ -11,7 +11,6 @@ support-files =
head.js
[browser_block_mozAddonManager.js]
[browser_dynamical_window_rounding.js]
[browser_navigator.js]
[browser_netInfo.js]
[browser_performanceAPI.js]

Просмотреть файл

@ -25,8 +25,6 @@ add_task(async function() {
isnot(tab3Zoom, tab1Zoom, "privacy.resistFingerprinting is true, site-specific zoom level should be disabled");
await FullZoom.reset();
BrowserTestUtils.removeTab(tab1);
BrowserTestUtils.removeTab(tab2);
BrowserTestUtils.removeTab(tab3);

Просмотреть файл

@ -1,281 +0,0 @@
/* 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/.
*
* Bug 1407366 - A test case for reassuring the size of the content viewport is
* rounded if the window is resized when letterboxing is enabled.
*
* A helpful note: if this test starts randomly failing; it may be because the
* zoom level was not reset by an earlier-run test. See Bug 1407366 for an
* example.
*/
const TEST_PATH = "http://example.net/browser/browser/components/resistfingerprinting/test/browser/";
const DEFAULT_ROUNDED_WIDTH_STEP = 200;
const DEFAULT_ROUNDED_HEIGHT_STEP = 100;
// A set of test cases which defines the width and the height of the outer window.
const TEST_CASES = [
{width: 1250, height: 1000},
{width: 1500, height: 1050},
{width: 1120, height: 760},
{width: 800, height: 600},
{width: 640, height: 400},
{width: 500, height: 350},
{width: 300, height: 170},
];
function getPlatform() {
const {OS} = Services.appinfo;
if (OS == "WINNT") {
return "win";
} else if (OS == "Darwin") {
return "mac";
}
return "linux";
}
function handleOSFuzziness(aContent, aTarget) {
/*
* On Windows, we observed off-by-one pixel differences that
* couldn't be expained. When manually setting the window size
* to try to reproduce it; it did not occur.
*/
if (getPlatform() == "win") {
return Math.abs(aContent - aTarget) <= 1;
}
return aContent == aTarget;
}
function checkForDefaultSetting(
aContentWidth, aContentHeight, aRealWidth, aRealHeight) {
// The default behavior for rounding is to round window with 200x100 stepping.
// So, we can get the rounded size by subtracting the remainder.
let targetWidth = aRealWidth - (aRealWidth % DEFAULT_ROUNDED_WIDTH_STEP);
let targetHeight = aRealHeight - (aRealHeight % DEFAULT_ROUNDED_HEIGHT_STEP);
// This platform-specific code is explained in the large comment below.
if (getPlatform() != "linux") {
ok(handleOSFuzziness(aContentWidth, targetWidth),
`Default Dimensions: The content window width is correctly rounded into. ${aRealWidth}px -> ${aContentWidth}px should equal ${targetWidth}px`);
ok(handleOSFuzziness(aContentHeight, targetHeight),
`Default Dimensions: The content window height is correctly rounded into. ${aRealHeight}px -> ${aContentHeight}px should equal ${targetHeight}px`);
// Using ok() above will cause Win/Mac to fail on even the first test, we don't need to repeat it, return true so waitForCondition ends
return true;
}
// Returning true or false depending on if the test succeeded will cause Linux to repeat until it succeeds.
return handleOSFuzziness(aContentWidth, targetWidth) && handleOSFuzziness(aContentHeight, targetHeight);
}
async function test_dynamical_window_rounding(aWindow, aCheckFunc) {
// We need to wait for the updating the margins for the newly opened tab, or
// it will affect the following tests.
let promiseForTheFirstRounding =
TestUtils.topicObserved("test:letterboxing:update-margin-finish");
info("Open a content tab for testing.");
let tab = await BrowserTestUtils.openNewForegroundTab(
aWindow.gBrowser, TEST_PATH + "file_dummy.html");
info("Wait until the margins are applied for the opened tab.");
await promiseForTheFirstRounding;
let getContainerSize = (aTab) => {
let browserContainer = aWindow.gBrowser
.getBrowserContainer(aTab.linkedBrowser);
return {
containerWidth: browserContainer.clientWidth,
containerHeight: browserContainer.clientHeight,
};
};
for (let {width, height} of TEST_CASES) {
let caseString = "Case " + width + "x" + height + ": ";
// Create a promise for waiting for the margin update.
let promiseRounding =
TestUtils.topicObserved("test:letterboxing:update-margin-finish");
let {containerWidth, containerHeight} = getContainerSize(tab);
info(caseString + "Resize the window and wait until resize event happened (currently " +
containerWidth + "x" + containerHeight + ")");
await new Promise(resolve => {
({containerWidth, containerHeight} = getContainerSize(tab));
info(caseString + "Resizing (currently " + containerWidth + "x" + containerHeight + ")");
aWindow.onresize = () => {
({containerWidth, containerHeight} = getContainerSize(tab));
info(caseString + "Resized (currently " + containerWidth + "x" + containerHeight + ")");
if (getPlatform() == "linux" && containerWidth != width) {
/*
* We observed frequent test failures that resulted from receiving an onresize
* event where the browser was resized to an earlier requested dimension. This
* resize event happens on Linux only, and is an artifact of the asynchronous
* resizing. (See more discussion on 1407366#53)
*
* We cope with this problem in two ways.
*
* 1: If we detect that the browser was resized to the wrong value; we
* redo the resize. (This is the lines of code immediately following this
* comment)
* 2: We repeat the test until it works using waitForCondition(). But we still
* test Win/Mac more thoroughly: they do not loop in waitForCondition more
* than once, and can fail the test on the first attempt (because their
* check() functions use ok() while on Linux, we do not all ok() and instead
* rely on waitForCondition to fail).
*
* The logging statements in this test, and RFPHelper.jsm, help narrow down and
* illustrate the issue.
*/
info(caseString + "We hit the weird resize bug. Resize it again.");
aWindow.resizeTo(width, height);
} else {
resolve();
}
};
aWindow.resizeTo(width, height);
});
({containerWidth, containerHeight} = getContainerSize(tab));
info(caseString + "Waiting until margin has been updated on browser element. (currently " +
containerWidth + "x" + containerHeight + ")");
await promiseRounding;
info(caseString + "Get innerWidth/Height from the content.");
await BrowserTestUtils.waitForCondition(async () => {
let {contentWidth, contentHeight} = await ContentTask.spawn(
tab.linkedBrowser, null, () => {
return {
contentWidth: content.innerWidth,
contentHeight: content.innerHeight,
};
});
info(caseString + "Check the result.");
return aCheckFunc(contentWidth, contentHeight, containerWidth, containerHeight);
}, "Default Dimensions: The content window width is correctly rounded into.");
}
BrowserTestUtils.removeTab(tab);
}
async function test_customize_width_and_height(aWindow) {
const test_dimensions = `120x80, 200x143, 335x255, 600x312, 742x447, 813x558,
990x672, 1200x733, 1470x858`;
await SpecialPowers.pushPrefEnv({"set":
[
["privacy.resistFingerprinting.letterboxing.dimensions", test_dimensions],
],
});
let dimensions_set = test_dimensions.split(",").map(item => {
let sizes = item.split("x").map(size => parseInt(size, 10));
return {
width: sizes[0],
height: sizes[1],
};
});
let checkDimension =
(aContentWidth, aContentHeight, aRealWidth, aRealHeight) => {
let matchingArea = aRealWidth * aRealHeight;
let minWaste = Number.MAX_SAFE_INTEGER;
let targetDimensions = undefined;
// Find the dimensions which waste the least content area.
for (let dim of dimensions_set) {
if (dim.width > aRealWidth || dim.height > aRealHeight) {
continue;
}
let waste = matchingArea - dim.width * dim.height;
if (waste >= 0 && waste < minWaste) {
targetDimensions = dim;
minWaste = waste;
}
}
// This platform-specific code is explained in the large comment above.
if (getPlatform() != "linux") {
ok(handleOSFuzziness(aContentWidth, targetDimensions.width),
`Custom Dimension: The content window width is correctly rounded into. ${aRealWidth}px -> ${aContentWidth}px should equal ${targetDimensions.width}`);
ok(handleOSFuzziness(aContentHeight, targetDimensions.height),
`Custom Dimension: The content window height is correctly rounded into. ${aRealHeight}px -> ${aContentHeight}px should equal ${targetDimensions.height}`);
// Using ok() above will cause Win/Mac to fail on even the first test, we don't need to repeat it, return true so waitForCondition ends
return true;
}
// Returning true or false depending on if the test succeeded will cause Linux to repeat until it succeeds.
return handleOSFuzziness(aContentWidth, targetDimensions.width) && handleOSFuzziness(aContentHeight, targetDimensions.height);
};
await test_dynamical_window_rounding(aWindow, checkDimension);
await SpecialPowers.popPrefEnv();
}
async function test_no_rounding_for_chrome(aWindow) {
// First, resize the window to a size with is not rounded.
await new Promise(resolve => {
aWindow.onresize = () => resolve();
aWindow.resizeTo(700, 450);
});
// open a chrome privilege tab, like about:config.
let tab = await BrowserTestUtils.openNewForegroundTab(
aWindow.gBrowser, "about:config");
// Check that the browser element should not have a margin.
is(tab.linkedBrowser.style.margin, "", "There is no margin around chrome tab.");
BrowserTestUtils.removeTab(tab);
}
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({"set":
[
["privacy.resistFingerprinting.letterboxing", true],
["privacy.resistFingerprinting.letterboxing.testing", true],
],
});
});
add_task(async function do_tests() {
// Store the original window size before testing.
let originalOuterWidth = window.outerWidth;
let originalOuterHeight = window.outerHeight;
info("Run test for the default window rounding.");
await test_dynamical_window_rounding(window, checkForDefaultSetting);
info("Run test for the window rounding with customized dimensions.");
await test_customize_width_and_height(window);
info("Run test for no margin around tab with the chrome privilege.");
await test_no_rounding_for_chrome(window);
// Restore the original window size.
window.outerWidth = originalOuterWidth;
window.outerHeight = originalOuterHeight;
// Testing that whether the dynamical rounding works for new windows.
let win = await BrowserTestUtils.openNewBrowserWindow();
info("Run test for the default window rounding in new window.");
await test_dynamical_window_rounding(win, checkForDefaultSetting);
info("Run test for the window rounding with customized dimensions in new window.");
await test_customize_width_and_height(win);
info("Run test for no margin around tab with the chrome privilege in new window.");
await test_no_rounding_for_chrome(win);
await BrowserTestUtils.closeWindow(win);
});

Просмотреть файл

@ -1442,9 +1442,6 @@ pref("privacy.firstparty.isolate.restrict_opener_access", true);
// If you do set it, to work around some broken website, please file a bug with
// information so we can understand why it is needed.
pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
// The log level for browser console messages logged in RFPHelper.jsm
// Change to 'All' and restart to see the messages
pref("privacy.resistFingerprinting.jsmloglevel", "Warn");
// A subset of Resist Fingerprinting protections focused specifically on timers for testing
// This affects the Animation API, the performance APIs, Date.getTime, Event.timestamp,
// File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime

Просмотреть файл

@ -0,0 +1,201 @@
// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["LanguagePrompt"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const kPrefResistFingerprinting = "privacy.resistFingerprinting";
const kPrefSpoofEnglish = "privacy.spoof_english";
const kTopicHttpOnModifyRequest = "http-on-modify-request";
class _LanguagePrompt {
constructor() {
this._initialized = false;
}
init() {
if (this._initialized) {
return;
}
this._initialized = true;
Services.prefs.addObserver(kPrefResistFingerprinting, this);
this._handleResistFingerprintingChanged();
}
uninit() {
if (!this._initialized) {
return;
}
this._initialized = false;
Services.prefs.removeObserver(kPrefResistFingerprinting, this);
this._removeObservers();
}
observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
this._handlePrefChanged(data);
break;
case kTopicHttpOnModifyRequest:
this._handleHttpOnModifyRequest(subject, data);
break;
default:
break;
}
}
_removeObservers() {
try {
Services.pref.removeObserver(kPrefSpoofEnglish, this);
} catch (e) {
// do nothing
}
try {
Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
} catch (e) {
// do nothing
}
}
_shouldPromptForLanguagePref() {
return (Services.locale.appLocaleAsLangTag.substr(0, 2) !== "en")
&& (Services.prefs.getIntPref(kPrefSpoofEnglish) === 0);
}
_handlePrefChanged(data) {
switch (data) {
case kPrefResistFingerprinting:
this._handleResistFingerprintingChanged();
break;
case kPrefSpoofEnglish:
this._handleSpoofEnglishChanged();
break;
default:
break;
}
}
_handleResistFingerprintingChanged() {
if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) {
Services.prefs.addObserver(kPrefSpoofEnglish, this);
if (this._shouldPromptForLanguagePref()) {
Services.obs.addObserver(this, kTopicHttpOnModifyRequest);
}
} else {
this._removeObservers();
}
}
_handleSpoofEnglishChanged() {
switch (Services.prefs.getIntPref(kPrefSpoofEnglish)) {
case 0: // will prompt
// This should only happen when turning privacy.resistFingerprinting off.
// Works like disabling accept-language spoofing.
case 1: // don't spoof
if (Services.prefs.prefHasUserValue("javascript.use_us_english_locale")) {
Services.prefs.clearUserPref("javascript.use_us_english_locale");
}
// We don't reset intl.accept_languages. Instead, setting
// privacy.spoof_english to 1 allows user to change preferred language
// settings through Preferences UI.
break;
case 2: // spoof
Services.prefs.setCharPref("intl.accept_languages", "en-US, en");
Services.prefs.setBoolPref("javascript.use_us_english_locale", true);
break;
default:
break;
}
}
_handleHttpOnModifyRequest(subject, data) {
// If we are loading an HTTP page from content, show the
// "request English language web pages?" prompt.
let httpChannel;
try {
httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
} catch (e) {
return;
}
if (!httpChannel) {
return;
}
let notificationCallbacks = httpChannel.notificationCallbacks;
if (!notificationCallbacks) {
return;
}
let loadContext = notificationCallbacks.getInterface(Ci.nsILoadContext);
if (!loadContext || !loadContext.isContent) {
return;
}
if (!subject.URI.schemeIs("http") && !subject.URI.schemeIs("https")) {
return;
}
// The above QI did not throw, the scheme is http[s], and we know the
// load context is content, so we must have a true HTTP request from content.
// Stop the observer and display the prompt if another window has
// not already done so.
Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
if (!this._shouldPromptForLanguagePref()) {
return;
}
this._promptForLanguagePreference();
// The Accept-Language header for this request was set when the
// channel was created. Reset it to match the value that will be
// used for future requests.
let val = this._getCurrentAcceptLanguageValue(subject.URI);
if (val) {
httpChannel.setRequestHeader("Accept-Language", val, false);
}
}
_promptForLanguagePreference() {
// Display two buttons, both with string titles.
let flags = Services.prompt.STD_YES_NO_BUTTONS;
let brandBundle = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
let brandShortName = brandBundle.GetStringFromName("brandShortName");
let navigatorBundle = Services.strings.createBundle(
"chrome://browser/locale/browser.properties");
let message = navigatorBundle.formatStringFromName(
"privacy.spoof_english", [brandShortName], 1);
let response = Services.prompt.confirmEx(
null, "", message, flags, null, null, null, null, {value: false});
// Update preferences to reflect their response and to prevent the prompt
// from being displayed again.
Services.prefs.setIntPref(kPrefSpoofEnglish, (response == 0) ? 2 : 1);
}
_getCurrentAcceptLanguageValue(uri) {
let channel = Services.io.newChannelFromURI2(
uri,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let httpChannel;
try {
httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
} catch (e) {
return null;
}
return httpChannel.getRequestHeader("Accept-Language");
}
}
let LanguagePrompt = new _LanguagePrompt();

Просмотреть файл

@ -1,531 +0,0 @@
// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["RFPHelper"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const kPrefResistFingerprinting = "privacy.resistFingerprinting";
const kPrefSpoofEnglish = "privacy.spoof_english";
const kTopicHttpOnModifyRequest = "http-on-modify-request";
const kPrefLetterboxing = "privacy.resistFingerprinting.letterboxing";
const kPrefLetterboxingDimensions =
"privacy.resistFingerprinting.letterboxing.dimensions";
const kPrefLetterboxingTesting =
"privacy.resistFingerprinting.letterboxing.testing";
const kTopicDOMWindowOpened = "domwindowopened";
const kEventLetterboxingSizeUpdate = "Letterboxing:ContentSizeUpdated";
const kDefaultWidthStepping = 200;
const kDefaultHeightStepping = 100;
var logConsole;
function log(msg) {
if (!logConsole) {
logConsole = console.createInstance({
prefix: "RFPHelper.jsm",
maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel",
});
}
logConsole.log(msg);
}
class _RFPHelper {
// ============================================================================
// Shared Setup
// ============================================================================
constructor() {
this._initialized = false;
}
init() {
if (this._initialized) {
return;
}
this._initialized = true;
// Add unconditional observers
Services.prefs.addObserver(kPrefResistFingerprinting, this);
Services.prefs.addObserver(kPrefLetterboxing, this);
XPCOMUtils.defineLazyPreferenceGetter(this, "_letterboxingDimensions",
kPrefLetterboxingDimensions, "", null, this._parseLetterboxingDimensions);
XPCOMUtils.defineLazyPreferenceGetter(this, "_isLetterboxingTesting",
kPrefLetterboxingTesting, false);
// Add RFP and Letterboxing observers if prefs are enabled
this._handleResistFingerprintingChanged();
this._handleLetterboxingPrefChanged();
}
uninit() {
if (!this._initialized) {
return;
}
this._initialized = false;
// Remove unconditional observers
Services.prefs.removeObserver(kPrefResistFingerprinting, this);
Services.prefs.removeObserver(kPrefLetterboxing, this);
// Remove the RFP observers, swallowing exceptions if they weren't present
this._removeRFPObservers();
}
observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
this._handlePrefChanged(data);
break;
case kTopicHttpOnModifyRequest:
this._handleHttpOnModifyRequest(subject, data);
break;
case kTopicDOMWindowOpened:
// We attach to the newly created window by adding tabsProgressListener
// and event listener on it. We listen for new tabs being added or
// the change of the content principal and apply margins accordingly.
this._handleDOMWindowOpened(subject);
break;
default:
break;
}
}
handleEvent(aMessage) {
switch (aMessage.type) {
case "TabOpen":
{
let tab = aMessage.target;
this._addOrClearContentMargin(tab.linkedBrowser);
break;
}
default:
break;
}
}
receiveMessage(aMessage) {
switch (aMessage.name) {
case kEventLetterboxingSizeUpdate:
let win = aMessage.target.ownerGlobal;
this._updateMarginsForTabsInWindow(win);
break;
default:
break;
}
}
_handlePrefChanged(data) {
switch (data) {
case kPrefResistFingerprinting:
this._handleResistFingerprintingChanged();
break;
case kPrefSpoofEnglish:
this._handleSpoofEnglishChanged();
break;
case kPrefLetterboxing:
this._handleLetterboxingPrefChanged();
break;
default:
break;
}
}
// ============================================================================
// Language Prompt
// ============================================================================
_addRFPObservers() {
Services.prefs.addObserver(kPrefSpoofEnglish, this);
if (this._shouldPromptForLanguagePref()) {
Services.obs.addObserver(this, kTopicHttpOnModifyRequest);
}
}
_removeRFPObservers() {
try {
Services.pref.removeObserver(kPrefSpoofEnglish, this);
} catch (e) {
// do nothing
}
try {
Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
} catch (e) {
// do nothing
}
}
_handleResistFingerprintingChanged() {
if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) {
this._addRFPObservers();
} else {
this._removeRFPObservers();
}
}
_handleSpoofEnglishChanged() {
switch (Services.prefs.getIntPref(kPrefSpoofEnglish)) {
case 0: // will prompt
// This should only happen when turning privacy.resistFingerprinting off.
// Works like disabling accept-language spoofing.
case 1: // don't spoof
if (Services.prefs.prefHasUserValue("javascript.use_us_english_locale")) {
Services.prefs.clearUserPref("javascript.use_us_english_locale");
}
// We don't reset intl.accept_languages. Instead, setting
// privacy.spoof_english to 1 allows user to change preferred language
// settings through Preferences UI.
break;
case 2: // spoof
Services.prefs.setCharPref("intl.accept_languages", "en-US, en");
Services.prefs.setBoolPref("javascript.use_us_english_locale", true);
break;
default:
break;
}
}
_shouldPromptForLanguagePref() {
return (Services.locale.appLocaleAsLangTag.substr(0, 2) !== "en")
&& (Services.prefs.getIntPref(kPrefSpoofEnglish) === 0);
}
_handleHttpOnModifyRequest(subject, data) {
// If we are loading an HTTP page from content, show the
// "request English language web pages?" prompt.
let httpChannel;
try {
httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
} catch (e) {
return;
}
if (!httpChannel) {
return;
}
let notificationCallbacks = httpChannel.notificationCallbacks;
if (!notificationCallbacks) {
return;
}
let loadContext = notificationCallbacks.getInterface(Ci.nsILoadContext);
if (!loadContext || !loadContext.isContent) {
return;
}
if (!subject.URI.schemeIs("http") && !subject.URI.schemeIs("https")) {
return;
}
// The above QI did not throw, the scheme is http[s], and we know the
// load context is content, so we must have a true HTTP request from content.
// Stop the observer and display the prompt if another window has
// not already done so.
Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
if (!this._shouldPromptForLanguagePref()) {
return;
}
this._promptForLanguagePreference();
// The Accept-Language header for this request was set when the
// channel was created. Reset it to match the value that will be
// used for future requests.
let val = this._getCurrentAcceptLanguageValue(subject.URI);
if (val) {
httpChannel.setRequestHeader("Accept-Language", val, false);
}
}
_promptForLanguagePreference() {
// Display two buttons, both with string titles.
let flags = Services.prompt.STD_YES_NO_BUTTONS;
let brandBundle = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
let brandShortName = brandBundle.GetStringFromName("brandShortName");
let navigatorBundle = Services.strings.createBundle(
"chrome://browser/locale/browser.properties");
let message = navigatorBundle.formatStringFromName(
"privacy.spoof_english", [brandShortName], 1);
let response = Services.prompt.confirmEx(
null, "", message, flags, null, null, null, null, {value: false});
// Update preferences to reflect their response and to prevent the prompt
// from being displayed again.
Services.prefs.setIntPref(kPrefSpoofEnglish, (response == 0) ? 2 : 1);
}
_getCurrentAcceptLanguageValue(uri) {
let channel = Services.io.newChannelFromURI2(
uri,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let httpChannel;
try {
httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
} catch (e) {
return null;
}
return httpChannel.getRequestHeader("Accept-Language");
}
// ==============================================================================
// Letterboxing
// ============================================================================
/**
* We use the TabsProgressListener to catch the change of the content
* principal. We would clear the margins around the content viewport if
* it is the system principal.
*/
onLocationChange(aBrowser) {
this._addOrClearContentMargin(aBrowser);
}
_handleLetterboxingPrefChanged() {
if (Services.prefs.getBoolPref(kPrefLetterboxing, false)) {
Services.ww.registerNotification(this);
this._attachAllWindows();
} else {
this._detachAllWindows();
Services.ww.unregisterNotification(this);
}
}
// The function to parse the dimension set from the pref value. The pref value
// should be formated as 'width1xheight1, width2xheight2, ...'. For
// example, '100x100, 200x200, 400x200 ...'.
_parseLetterboxingDimensions(aPrefValue) {
if (!aPrefValue || !aPrefValue.match(/^(?:\d+x\d+,\s*)*(?:\d+x\d+)$/)) {
if (aPrefValue) {
Cu.reportError(`Invalid pref value for ${kPrefLetterboxingDimensions}: ${aPrefValue}`);
}
return [];
}
return aPrefValue.split(",").map(item => {
let sizes = item.split("x").map(size => parseInt(size, 10));
return {
width: sizes[0],
height: sizes[1],
};
});
}
_addOrClearContentMargin(aBrowser) {
let tab = aBrowser.getTabBrowser()
.getTabForBrowser(aBrowser);
// We won't do anything for lazy browsers.
if (!aBrowser.isConnected) {
return;
}
// We should apply no margin around an empty tab or a tab with system
// principal.
if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) {
this._clearContentViewMargin(aBrowser);
} else {
this._roundContentView(aBrowser);
}
}
/**
* The function will round the given browser by adding margins around the
* content viewport.
*/
async _roundContentView(aBrowser) {
let logId = Math.random();
log("_roundContentView[" + logId + "]");
let win = aBrowser.ownerGlobal;
let browserContainer = aBrowser.getTabBrowser()
.getBrowserContainer(aBrowser);
let {contentWidth, contentHeight, containerWidth, containerHeight} =
await win.promiseDocumentFlushed(() => {
let contentWidth = aBrowser.clientWidth;
let contentHeight = aBrowser.clientHeight;
let containerWidth = browserContainer.clientWidth;
let containerHeight = browserContainer.clientHeight;
return {
contentWidth,
contentHeight,
containerWidth,
containerHeight,
};
});
log("_roundContentView[" + logId + "] contentWidth=" + contentWidth + " contentHeight=" + contentHeight +
" containerWidth=" + containerWidth + " containerHeight=" + containerHeight + " ");
let calcMargins = (aWidth, aHeight) => {
let result;
log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ")");
// If the set is empty, we will round the content with the default
// stepping size.
if (!this._letterboxingDimensions.length) {
result = {
width: (aWidth % kDefaultWidthStepping) / 2,
height: (aHeight % kDefaultHeightStepping) / 2,
};
log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ") = " + result.width + " x " + result.height);
return result;
}
let matchingArea = aWidth * aHeight;
let minWaste = Number.MAX_SAFE_INTEGER;
let targetDimensions = undefined;
// Find the desired dimensions which waste the least content area.
for (let dim of this._letterboxingDimensions) {
// We don't need to consider the dimensions which cannot fit into the
// real content size.
if (dim.width > aWidth || dim.height > aHeight) {
continue;
}
let waste = matchingArea - dim.width * dim.height;
if (waste >= 0 && waste < minWaste) {
targetDimensions = dim;
minWaste = waste;
}
}
// If we cannot find any dimensions match to the real content window, this
// means the content area is smaller the smallest size in the set. In this
// case, we won't apply any margins.
if (!targetDimensions) {
result = {
width: 0,
height: 0,
};
} else {
result = {
width: (aWidth - targetDimensions.width) / 2,
height: (aHeight - targetDimensions.height) / 2,
};
}
log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ") = " + result.width + " x " + result.height);
return result;
};
// Calculating the margins around the browser element in order to round the
// content viewport. We will use a 200x100 stepping if the dimension set
// is not given.
let margins = calcMargins(containerWidth, containerHeight);
// If the size of the content is already quantized, we do nothing.
if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) {
log("_roundContentView[" + logId + "] is_rounded == true");
if (this._isLetterboxingTesting) {
log("_roundContentView[" + logId + "] is_rounded == true test:letterboxing:update-margin-finish");
Services.obs.notifyObservers(null, "test:letterboxing:update-margin-finish");
}
return;
}
win.requestAnimationFrame(() => {
log("_roundContentView[" + logId + "] setting margins to " + margins.width + " x " + margins.height);
// One cannot (easily) control the color of a margin unfortunately.
// An initial attempt to use a border instead of a margin resulted
// in offset event dispatching; so for now we use a colorless margin.
aBrowser.style.margin = `${margins.height}px ${margins.width}px`;
});
}
_clearContentViewMargin(aBrowser) {
aBrowser.ownerGlobal.requestAnimationFrame(() => {
aBrowser.style.margin = "";
});
}
_updateMarginsForTabsInWindow(aWindow) {
let tabBrowser = aWindow.gBrowser;
for (let tab of tabBrowser.tabs) {
let browser = tab.linkedBrowser;
this._addOrClearContentMargin(browser);
}
}
_attachWindow(aWindow) {
aWindow.gBrowser
.addTabsProgressListener(this);
aWindow.addEventListener("TabOpen", this);
aWindow.messageManager
.addMessageListener(kEventLetterboxingSizeUpdate, this);
// Rounding the content viewport.
this._updateMarginsForTabsInWindow(aWindow);
}
_attachAllWindows() {
let windowList = Services.wm.getEnumerator("navigator:browser");
while (windowList.hasMoreElements()) {
let win = windowList.getNext();
if (win.closed || !win.gBrowser) {
continue;
}
this._attachWindow(win);
}
}
_detachWindow(aWindow) {
let tabBrowser = aWindow.gBrowser;
tabBrowser.removeTabsProgressListener(this);
aWindow.removeEventListener("TabOpen", this);
aWindow.messageManager
.removeMessageListener(kEventLetterboxingSizeUpdate, this);
// Clear all margins and tooltip for all browsers.
for (let tab of tabBrowser.tabs) {
let browser = tab.linkedBrowser;
this._clearContentViewMargin(browser);
}
}
_detachAllWindows() {
let windowList = Services.wm.getEnumerator("navigator:browser");
while (windowList.hasMoreElements()) {
let win = windowList.getNext();
if (win.closed || !win.gBrowser) {
continue;
}
this._detachWindow(win);
}
}
_handleDOMWindowOpened(aSubject) {
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
let self = this;
win.addEventListener("load", () => {
// We attach to the new window when it has been loaded if the new loaded
// window is a browsing window.
if (win.document
.documentElement
.getAttribute("windowtype") !== "navigator:browser") {
return;
}
self._attachWindow(win);
}, {once: true});
}
}
let RFPHelper = new _RFPHelper();

Просмотреть файл

@ -21,5 +21,5 @@ EXPORTS.mozilla += [
]
EXTRA_JS_MODULES += [
'RFPHelper.jsm',
'LanguagePrompt.jsm',
]