Bug 1390161 - Show that a WebExtension is managing the tracking protection setting, r=jaws,mstriemer

This adjusts both the new Tracking Protection UI and the old Tracking Protection UI on
about:preferences to indicate when an extension is controlling Tracking Protection. It
will disable the controls on about:preferences if an extension is in control, and provide
a button to disable the extension.

MozReview-Commit-ID: G04jWrS6Pr9

--HG--
extra : rebase_source : 4cdee73b00b74e25c074e62a872d7b50a984cf8f
This commit is contained in:
Bob Silverberg 2017-11-06 12:41:48 -05:00
Родитель dd155c0445
Коммит 31cb7a05fa
7 изменённых файлов: 303 добавлений и 43 удалений

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

@ -37,7 +37,6 @@ const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
"browser.download.hide_plugins_without_extensions";
// Strings to identify ExtensionSettingsStore overrides
const PREF_SETTING_TYPE = "prefs";
const CONTAINERS_KEY = "privacy.containers";
const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
const URL_OVERRIDES_TYPE = "url_overrides";

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

@ -31,6 +31,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
XPCOMUtils.defineLazyModuleGetter(this, "formAutofillParent",
"resource://formautofill/FormAutofillParent.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
"privacy.trackingprotection.ui.enabled");
var gLastHash = "";
var gCategoryInits = new Map();
@ -418,11 +421,22 @@ function appendSearchKeywords(aId, keywords) {
element.setAttribute("searchkeywords", keywords.join(" "));
}
const PREF_SETTING_TYPE = "prefs";
let extensionControlledContentIds = {
"privacy.containers": "browserContainersExtensionContent",
"homepage_override": "browserHomePageExtensionContent",
"newTabURL": "browserNewTabExtensionContent",
"defaultSearch": "browserDefaultSearchExtensionContent",
get "websites.trackingProtectionMode"() {
return {
button: "trackingProtectionExtensionContentButton",
section:
trackingprotectionUiEnabled ?
"trackingProtectionExtensionContentLabel" :
"trackingProtectionPBMExtensionContentLabel",
};
}
};
let extensionControlledIds = {};
@ -435,8 +449,17 @@ async function getControllingExtensionInfo(type, settingName) {
return ExtensionSettingsStore.getSetting(type, settingName);
}
function getControllingExtensionEl(settingName) {
return document.getElementById(extensionControlledContentIds[settingName]);
function getControllingExtensionEls(settingName) {
let idInfo = extensionControlledContentIds[settingName];
let section = document.getElementById(idInfo.section || idInfo);
let button = idInfo.button ?
document.getElementById(idInfo.button) :
section.querySelector("button");
return {
section,
button,
description: section.querySelector("description"),
};
}
async function handleControllingExtension(type, settingName) {
@ -453,7 +476,10 @@ async function handleControllingExtension(type, settingName) {
extensionControlledIds[settingName] = info.id;
showControllingExtension(settingName, addon);
} else {
if (extensionControlledIds[settingName] && !document.hidden) {
let elements = getControllingExtensionEls(settingName);
if (extensionControlledIds[settingName]
&& !document.hidden
&& elements.button) {
showEnableExtensionMessage(settingName);
} else {
hideControllingExtension(settingName);
@ -466,14 +492,15 @@ async function handleControllingExtension(type, settingName) {
async function showControllingExtension(settingName, addon) {
// Tell the user what extension is controlling the setting.
let extensionControlledContent = getControllingExtensionEl(settingName);
extensionControlledContent.classList.remove("extension-controlled-disabled");
let elements = getControllingExtensionEls(settingName);
elements.section.classList.remove("extension-controlled-disabled");
const defaultIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
let stringParts = document
.getElementById("bundlePreferences")
.getString(`extensionControlled.${settingName}`)
.split("%S");
let description = extensionControlledContent.querySelector("description");
let description = elements.description;
// Remove the old content from the description.
while (description.firstChild) {
@ -489,38 +516,42 @@ async function showControllingExtension(settingName, addon) {
description.appendChild(document.createTextNode(` ${addon.name}`));
description.appendChild(document.createTextNode(stringParts[1]));
let disableButton = extensionControlledContent.querySelector("button");
if (disableButton) {
disableButton.hidden = false;
if (elements.button) {
elements.button.hidden = false;
}
// Show the controlling extension row and hide the old label.
extensionControlledContent.hidden = false;
elements.section.hidden = false;
}
function hideControllingExtension(settingName) {
getControllingExtensionEl(settingName).hidden = true;
let elements = getControllingExtensionEls(settingName);
elements.section.hidden = true;
if (elements.button) {
elements.button.hidden = true;
}
}
function showEnableExtensionMessage(settingName) {
let extensionControlledContent = getControllingExtensionEl(settingName);
extensionControlledContent.classList.add("extension-controlled-disabled");
let elements = getControllingExtensionEls(settingName);
elements.button.hidden = true;
elements.section.classList.add("extension-controlled-disabled");
let icon = url => `<image src="${url}" class="extension-controlled-icon"/>`;
let addonIcon = icon("chrome://mozapps/skin/extensions/extensionGeneric-16.svg");
let toolbarIcon = icon("chrome://browser/skin/menu.svg");
let message = document
.getElementById("bundlePreferences")
.getFormattedString("extensionControlled.enable", [addonIcon, toolbarIcon]);
let description = extensionControlledContent.querySelector("description");
// eslint-disable-next-line no-unsanitized/property
description.innerHTML = message;
elements.description.innerHTML = message;
let dismissButton = document.createElement("image");
dismissButton.setAttribute("class", "extension-controlled-icon close-icon");
dismissButton.addEventListener("click", function dismissHandler() {
hideControllingExtension(settingName);
dismissButton.removeEventListener("click", dismissHandler);
});
description.appendChild(dismissButton);
elements.description.appendChild(dismissButton);
}
function makeDisableControllingExtension(type, settingName) {

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

@ -17,10 +17,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
"resource:///modules/SiteDataManager.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "trackingprotectionUiEnabled",
"privacy.trackingprotection.ui.enabled");
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
const TRACKING_PROTECTION_PREFS = ["privacy.trackingprotection.enabled",
"privacy.trackingprotection.pbmode.enabled"];
XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
try {
let alertsService = Cc["@mozilla.org/alerts-service;1"]
@ -133,7 +140,7 @@ var gPrivacyPane = {
* privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
*/
_initTrackingProtection() {
if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
if (!trackingprotectionUiEnabled) {
return;
}
@ -153,9 +160,71 @@ var gPrivacyPane = {
* Protection UI.
*/
_initTrackingProtectionPBM() {
let link = document.getElementById("trackingProtectionPBMLearnMore");
if (trackingprotectionUiEnabled) {
return;
}
let link = document.getElementById("trackingProtectionLearnMore");
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection-pbm";
link.setAttribute("href", url);
this._updateTrackingProtectionUI();
},
/**
* Update the tracking protection UI to deal with extension control.
*/
_updateTrackingProtectionUI() {
let isLocked = TRACKING_PROTECTION_PREFS.some(
pref => Services.prefs.prefIsLocked(pref));
function setInputsDisabledState(isControlled) {
let disabled = isLocked || isControlled;
if (trackingprotectionUiEnabled) {
document.querySelectorAll("#trackingProtectionRadioGroup > radio")
.forEach((element) => {
element.disabled = disabled;
});
document.querySelector("#trackingProtectionDesc > label")
.disabled = disabled;
} else {
document.getElementById("trackingProtectionPBM").disabled = disabled;
document.getElementById("trackingProtectionPBMLabel")
.disabled = disabled;
}
}
if (isLocked) {
// An extension can't control this setting if either pref is locked.
hideControllingExtension(TRACKING_PROTECTION_KEY);
setInputsDisabledState(false);
} else {
handleControllingExtension(
PREF_SETTING_TYPE,
TRACKING_PROTECTION_KEY)
.then(setInputsDisabledState);
}
},
/**
* Set up handlers for showing and hiding controlling extension info
* for tracking protection.
*/
_initTrackingProtectionExtensionControl() {
let trackingProtectionObserver = {
observe(subject, topic, data) {
gPrivacyPane._updateTrackingProtectionUI();
},
};
for (let pref of TRACKING_PROTECTION_PREFS) {
Services.prefs.addObserver(pref, trackingProtectionObserver);
}
window.addEventListener("unload", () => {
for (let pref of TRACKING_PROTECTION_PREFS) {
Services.prefs.removeObserver(pref, trackingProtectionObserver);
}
});
},
/**
@ -183,6 +252,7 @@ var gPrivacyPane = {
this.initAutoStartPrivateBrowsingReverter();
this._initTrackingProtection();
this._initTrackingProtectionPBM();
this._initTrackingProtectionExtensionControl();
this._initAutocomplete();
Preferences.get("privacy.sanitize.sanitizeOnShutdown").on("change",
@ -227,6 +297,9 @@ var gPrivacyPane = {
gPrivacyPane.showCookies);
setEventListener("clearDataSettings", "command",
gPrivacyPane.showClearPrivateDataSettings);
setEventListener("disableTrackingProtectionExtension", "command",
makeDisableControllingExtension(
PREF_SETTING_TYPE, TRACKING_PROTECTION_KEY));
setEventListener("trackingProtectionRadioGroup", "command",
gPrivacyPane.trackingProtectionWritePrefs);
setEventListener("trackingProtectionExceptions", "command",
@ -419,6 +492,8 @@ var gPrivacyPane = {
let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
let radiogroup = document.getElementById("trackingProtectionRadioGroup");
this._updateTrackingProtectionUI();
// Global enable takes precedence over enabled in Private Browsing.
if (enabledPref.value) {
radiogroup.value = "always";

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

@ -323,41 +323,55 @@
<hbox align="start">
<vbox flex="1">
<description>
&trackingProtection2.description;
&trackingProtection3.description;
<label id="trackingProtectionLearnMore" class="learnMore text-link">&trackingProtectionLearnMore2.label;</label>
</description>
</vbox>
<spacer flex="1"/>
</hbox>
<hbox>
<vbox id="trackingProtectionBox" flex="1" hidden="true">
<description id="trackingProtectionDesc"
control="trackingProtectionRadioGroup">
<label class="tail-with-learn-more">&trackingProtection2.radioGroupLabel;</label>
<label id="trackingProtectionLearnMore" class="learnMore text-link">&trackingProtectionLearnMore.label;</label>
</description>
<radiogroup id="trackingProtectionRadioGroup" aria-labelledby="trackingProtectionDesc">
<radio value="always"
label="&trackingProtectionAlways.label;"
accesskey="&trackingProtectionAlways.accesskey;"/>
<radio value="private"
label="&trackingProtectionPrivate.label;"
accesskey="&trackingProtectionPrivate.accesskey;"/>
<radio value="never"
label="&trackingProtectionNever.label;"
accesskey="&trackingProtectionNever.accesskey;"/>
</radiogroup>
<vbox>
<hbox id="trackingProtectionExtensionContentLabel" align="center" hidden="true">
<description control="disableTrackingProtectionExtension" flex="1"/>
</hbox>
<vbox>
<description id="trackingProtectionDesc"
control="trackingProtectionRadioGroup">
<label>&trackingProtection3.radioGroupLabel;</label>
</description>
<radiogroup id="trackingProtectionRadioGroup" aria-labelledby="trackingProtectionDesc">
<radio value="always"
label="&trackingProtectionAlways.label;"
accesskey="&trackingProtectionAlways.accesskey;"/>
<radio value="private"
label="&trackingProtectionPrivate.label;"
accesskey="&trackingProtectionPrivate.accesskey;"/>
<radio value="never"
label="&trackingProtectionNever.label;"
accesskey="&trackingProtectionNever.accesskey;"/>
</radiogroup>
</vbox>
</vbox>
</vbox>
<vbox id="trackingProtectionPBMBox" flex="1">
<hbox align="center">
<hbox id="trackingProtectionPBMExtensionContentLabel" align="center" hidden="true">
<description control="disableTrackingProtectionExtension" flex="1"/>
</hbox>
<hbox align="start">
<checkbox id="trackingProtectionPBM"
preference="privacy.trackingprotection.pbmode.enabled"
accesskey="&trackingProtectionPBM6.accesskey;"/>
<label flex="1">&trackingProtectionPBM6.label;<spacer class="tail-with-learn-more" /><label id="trackingProtectionPBMLearnMore"
class="learnMore text-link">&trackingProtectionPBMLearnMore.label;</label>
</label>
<label id="trackingProtectionPBMLabel" flex="1">&trackingProtectionPBM6.label;</label>
</hbox>
</vbox>
<vbox id="trackingProtectionAdvancedSettings">
<hbox id="trackingProtectionExtensionContentButton" hidden="true">
<button id="disableTrackingProtectionExtension"
class="extension-controlled-button accessory-button"
flex="1"
label="&disableExtension.label;"/>
</hbox>
<!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
<hbox>
<button id="trackingProtectionExceptions"

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

@ -1,3 +1,5 @@
/* eslint-env webextensions */
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSettingsStore",
"resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
@ -467,3 +469,138 @@ add_task(async function testExtensionControlledHomepageUninstalledAddon() {
is(ExtensionSettingsStore.getSetting("prefs", "homepage_override"), null,
"The ExtensionSettingsStore is left empty.");
});
add_task(async function testExtensionControlledTrackingProtection() {
const TP_UI_PREF = "privacy.trackingprotection.ui.enabled";
const TP_PREF = "privacy.trackingprotection.enabled";
const TP_DEFAULT = false;
const EXTENSION_ID = "@set_tp";
const CONTROLLED_LABEL_ID = {
new: "trackingProtectionExtensionContentLabel",
old: "trackingProtectionPBMExtensionContentLabel"
};
const CONTROLLED_BUTTON_ID = "trackingProtectionExtensionContentButton";
let tpEnabledPref = () => Services.prefs.getBoolPref(TP_PREF);
await SpecialPowers.pushPrefEnv(
{"set": [[TP_PREF, TP_DEFAULT], [TP_UI_PREF, true]]});
function background() {
browser.privacy.websites.trackingProtectionMode.set({value: "always"});
}
function verifyState(isControlled) {
is(tpEnabledPref(), isControlled, "TP pref is set to the expected value.");
let controlledLabel = doc.getElementById(CONTROLLED_LABEL_ID[uiType]);
is(controlledLabel.hidden, !isControlled, "The extension controlled row's visibility is as expected.");
is(controlledButton.hidden, !isControlled, "The disable extension button's visibility is as expected.");
if (isControlled) {
let controlledDesc = controlledLabel.querySelector("description");
// There are two spaces before "set_tp" because it's " <image /> set_tp".
is(controlledDesc.textContent, "An extension, set_tp, is controlling tracking protection.",
"The user is notified that an extension is controlling TP.");
}
if (uiType === "new") {
for (let element of doc.querySelectorAll("#trackingProtectionRadioGroup > radio")) {
is(element.disabled, isControlled, "TP controls are enabled.");
}
is(doc.querySelector("#trackingProtectionDesc > label").disabled,
isControlled,
"TP control label is enabled.");
} else {
is(doc.getElementById("trackingProtectionPBM").disabled,
isControlled,
"TP control is enabled.");
is(doc.getElementById("trackingProtectionPBMLabel").disabled,
isControlled,
"TP control label is enabled.");
}
}
async function disableViaClick() {
let labelId = CONTROLLED_LABEL_ID[uiType];
let controlledLabel = doc.getElementById(labelId);
let enableMessageShown = waitForEnableMessage(labelId);
doc.getElementById("disableTrackingProtectionExtension").click();
await enableMessageShown;
// The user is notified how to enable the extension.
let controlledDesc = controlledLabel.querySelector("description");
is(controlledDesc.textContent, "To enable the extension go to Add-ons in the menu.",
"The user is notified of how to enable the extension again");
// The user can dismiss the enable instructions.
let hidden = waitForMessageHidden(labelId);
controlledLabel.querySelector("image:last-of-type").click();
await hidden;
}
async function reEnableExtension(addon) {
let controlledMessageShown = waitForMessageShown(CONTROLLED_LABEL_ID[uiType]);
addon.userDisabled = false;
await controlledMessageShown;
}
let uiType = "new";
await openPreferencesViaOpenPreferencesAPI("panePrivacy", {leaveOpen: true});
// eslint-disable-next-line mozilla/no-cpows-in-tests
let doc = gBrowser.contentDocument;
is(gBrowser.currentURI.spec, "about:preferences#privacy",
"#privacy should be in the URI for about:preferences");
let controlledButton = doc.getElementById(CONTROLLED_BUTTON_ID);
verifyState(false);
// Install an extension that sets Tracking Protection.
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
name: "set_tp",
applications: {gecko: {id: EXTENSION_ID}},
permissions: ["privacy"],
},
background,
});
let messageShown = waitForMessageShown(CONTROLLED_LABEL_ID[uiType]);
await extension.startup();
await messageShown;
let addon = await AddonManager.getAddonByID(EXTENSION_ID);
verifyState(true);
await disableViaClick();
verifyState(false);
// Switch to the "old" Tracking Protection UI.
uiType = "old";
Services.prefs.setBoolPref(TP_UI_PREF, false);
verifyState(false);
await reEnableExtension(addon);
verifyState(true);
await disableViaClick();
verifyState(false);
// Enable the extension so we get the UNINSTALL event, which is needed by
// ExtensionPreferencesManager to clean up properly.
// TODO: BUG 1408226
await reEnableExtension(addon);
await extension.unload();
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

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

@ -292,6 +292,10 @@ extensionControlled.defaultSearch = An extension, %S, has set your default searc
# %S is the container addon controlling it
extensionControlled.privacy.containers = An extension, %S, requires Container Tabs.
# LOCALIZATION NOTE (extensionControlled.websites.trackingProtectionMode):
# This string is shown to notify the user that their tracking protection preferences are being controlled by an extension.
extensionControlled.websites.trackingProtectionMode = An extension, %S, is controlling tracking protection.
# LOCALIZATION NOTE (extensionControlled.enable):
# %1$S is replaced with the icon for the add-ons menu.
# %2$S is replaced with the icon for the toolbar menu.

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

@ -3,15 +3,15 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!ENTITY trackingProtectionHeader2.label "Tracking Protection">
<!ENTITY trackingProtection2.description "Tracking is the collection of your browsing data across multiple websites. Tracking can be used to build a profile and display content based on your browsing and personal information.">
<!ENTITY trackingProtection2.radioGroupLabel "Use Tracking Protection to block known trackers">
<!ENTITY trackingProtection3.description "Tracking Protection blocks online trackers that collect your browsing data across multiple websites.">
<!ENTITY trackingProtection3.radioGroupLabel "Use Tracking Protection to block known trackers">
<!ENTITY trackingProtectionAlways.label "Always">
<!ENTITY trackingProtectionAlways.accesskey "y">
<!ENTITY trackingProtectionPrivate.label "Only in private windows">
<!ENTITY trackingProtectionPrivate.accesskey "l">
<!ENTITY trackingProtectionNever.label "Never">
<!ENTITY trackingProtectionNever.accesskey "n">
<!ENTITY trackingProtectionLearnMore.label "Learn more">
<!ENTITY trackingProtectionLearnMore2.label "Learn more about Tracking Protection and your privacy">
<!ENTITY trackingProtectionExceptions.label "Exceptions…">
<!ENTITY trackingProtectionExceptions.accesskey "x">