Bug 1354602 - Enabling containers for container addons on startup. r?zombie r?mconley
MozReview-Commit-ID: BXLyQz8CGDl
--- a/browser/components/preferences/in-content-new/main.js
+++ b/browser/components/preferences/in-content-new/main.js
@@ -943,17 +943,16 @@ var gMainPane = {
let cancelButton = bundlePreferences.getString("disableContainersButton2");
let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
okButton, cancelButton, null, null, {});
if (rv == 0) {
- ContextualIdentityService.closeContainerTabs();
Services.prefs.setBoolPref("privacy.userContext.enabled", false);
return;
}
checkbox.checked = true;
},
/**
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -82,17 +82,16 @@ var gPrivacyPane = {
let checkbox = document.getElementById("browserContainersCheckbox");
if (checkbox.checked) {
Services.prefs.setBoolPref("privacy.userContext.enabled", true);
return;
}
let count = ContextualIdentityService.countContainerTabs();
if (count == 0) {
- ContextualIdentityService.notifyAllContainersCleared();
Services.prefs.setBoolPref("privacy.userContext.enabled", false);
return;
}
let bundlePreferences = document.getElementById("bundlePreferences");
let title = bundlePreferences.getString("disableContainersAlertTitle");
let message = PluralForm.get(count, bundlePreferences.getString("disableContainersMsg"))
@@ -103,19 +102,16 @@ var gPrivacyPane = {
let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
okButton, cancelButton, null, null, {});
if (rv == 0) {
Services.prefs.setBoolPref("privacy.userContext.enabled", false);
- ContextualIdentityService.closeContainerTabs().then(() => {
- ContextualIdentityService.notifyAllContainersCleared();
- });
return;
}
checkbox.checked = true;
},
/**
* Sets up the UI for the number of days of history to keep, and updates the
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -6,16 +6,17 @@ this.EXPORTED_SYMBOLS = ["ContextualIden
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const DEFAULT_TAB_COLOR = "#909090";
const SAVE_DELAY_MS = 1500;
+const CONTEXTUAL_IDENTITY_ENABLED_PREF = "privacy.userContext.enabled";
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle("chrome://browser/locale/browser.properties");
});
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function() {
return new TextDecoder();
});
@@ -109,16 +110,28 @@ function _ContextualIdentityService(path
_path: null,
_dataReady: false,
_saver: null,
init(path) {
this._path = path;
+
+ Services.prefs.addObserver(CONTEXTUAL_IDENTITY_ENABLED_PREF, this);
+ },
+
+ // observe() is only used to listen to container enabling pref
+ async observe() {
+ const contextualIdentitiesEnabled = Services.prefs.getBoolPref(CONTEXTUAL_IDENTITY_ENABLED_PREF);
+ if (!contextualIdentitiesEnabled) {
+ await this.closeContainerTabs();
+ this.notifyAllContainersCleared();
+ this.resetDefault();
+ }
},
load() {
OS.File.read(this._path).then(bytes => {
// If synchronous loading happened in the meantime, exit now.
if (this._dataReady) {
return;
}
@@ -142,18 +155,23 @@ function _ContextualIdentityService(path
this.loadError(error);
}
}, (error) => {
this.loadError(error);
});
},
resetDefault() {
- this._identities = this._defaultIdentities;
+ this._identities = [];
+ // Clone the array
this._lastUserContextId = this._defaultIdentities.length;
+ for (let i = 0; i < this._lastUserContextId; i++) {
+ this._identities.push(Object.assign({}, this._defaultIdentities[i]));
+ }
+ this._openedIdentities = new Set();
this._dataReady = true;
this.saveSoon();
},
loadError(error) {
if (error != null &&
--- a/toolkit/components/extensions/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/ext-contextualIdentities.js
@@ -3,16 +3,27 @@
// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
XPCOMUtils.defineLazyPreferenceGetter(this, "containersEnabled",
"privacy.userContext.enabled");
+Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
+
+const CONTAINER_PREF_INSTALL_DEFAULTS = {
+ "privacy.userContext.enabled": true,
+ "privacy.userContext.longPressBehavior": 2,
+ "privacy.userContext.ui.enabled": true,
+ "privacy.usercontext.about_newtab_segregation.enabled": true,
+};
+
+const CONTAINERS_ENABLED_SETTING_NAME = "privacy.containers";
+
const convertIdentity = identity => {
let result = {
name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
icon: identity.icon,
color: identity.color,
cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
};
@@ -26,68 +37,76 @@ const convertIdentityFromObserver = wrap
icon: identity.icon,
color: identity.color,
cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
};
return result;
};
+ExtensionPreferencesManager.addSetting(CONTAINERS_ENABLED_SETTING_NAME, {
+ prefNames: Object.keys(CONTAINER_PREF_INSTALL_DEFAULTS),
+
+ setCallback(value) {
+ if (value === true) {
+ return CONTAINER_PREF_INSTALL_DEFAULTS;
+ }
+
+ let prefs = {};
+ for (let pref of this.prefNames) {
+ prefs[pref] = undefined;
+ }
+ return prefs;
+ },
+});
+
this.contextualIdentities = class extends ExtensionAPI {
+ onStartup() {
+ let {extension} = this;
+
+ if (extension.hasPermission("contextualIdentities")) {
+ ExtensionPreferencesManager.setSetting(extension, CONTAINERS_ENABLED_SETTING_NAME, true);
+ }
+ }
+
getAPI(context) {
let self = {
contextualIdentities: {
get(cookieStoreId) {
- if (!containersEnabled) {
- return Promise.resolve(false);
- }
-
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
return Promise.resolve(convertIdentity(identity));
},
query(details) {
- if (!containersEnabled) {
- return Promise.resolve(false);
- }
-
let identities = [];
ContextualIdentityService.getPublicIdentities().forEach(identity => {
if (details.name &&
ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
return;
}
identities.push(convertIdentity(identity));
});
return Promise.resolve(identities);
},
create(details) {
- if (!containersEnabled) {
- return Promise.resolve(false);
- }
-
let identity = ContextualIdentityService.create(details.name,
details.icon,
details.color);
return Promise.resolve(convertIdentity(identity));
},
update(cookieStoreId, details) {
- if (!containersEnabled) {
- return Promise.resolve(false);
- }
-
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
@@ -110,20 +129,16 @@ this.contextualIdentities = class extend
identity.color)) {
return Promise.resolve(null);
}
return Promise.resolve(convertIdentity(identity));
},
remove(cookieStoreId) {
- if (!containersEnabled) {
- return Promise.resolve(false);
- }
-
let containerId = getContainerForCookieStoreId(cookieStoreId);
if (!containerId) {
return Promise.resolve(null);
}
let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
if (!identity) {
return Promise.resolve(null);
--- a/toolkit/components/extensions/ext-toolkit.json
+++ b/toolkit/components/extensions/ext-toolkit.json
@@ -23,16 +23,17 @@
"paths": [
["browserSettings"]
]
},
"contextualIdentities": {
"url": "chrome://extensions/content/ext-contextualIdentities.js",
"schema": "chrome://extensions/content/schemas/contextual_identities.json",
"scopes": ["addon_parent"],
+ "events": ["startup"],
"paths": [
["contextualIdentities"]
]
},
"cookies": {
"url": "chrome://extensions/content/ext-cookies.js",
"schema": "chrome://extensions/content/schemas/cookies.json",
"scopes": ["addon_parent"],
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -1,73 +1,44 @@
"use strict";
do_get_profile();
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://modules/AddonManager.jsm");
+add_task(async function startup() {
+ await ExtensionTestUtils.startAddonManager();
+});
+
add_task(async function test_contextualIdentities_without_permissions() {
- function backgroundScript() {
+ function background() {
browser.test.assertTrue(!browser.contextualIdentities,
"contextualIdentities API is not available when the contextualIdentities permission is not required");
- browser.test.notifyPass("contextualIdentities_permission");
+ browser.test.notifyPass("contextualIdentities_without_permission");
}
let extension = ExtensionTestUtils.loadExtension({
- background: `(${backgroundScript})()`,
+ useAddonManager: "temporary",
+ background,
manifest: {
+ applications: {
+ gecko: {id: "testing@thing.com"},
+ },
permissions: [],
},
});
await extension.startup();
- await extension.awaitFinish("contextualIdentities_permission");
+ await extension.awaitFinish("contextualIdentities_without_permission");
await extension.unload();
});
-
-add_task(async function test_contextualIdentity_no_containers() {
- async function backgroundScript() {
- let ci = await browser.contextualIdentities.get("foobar");
- browser.test.assertEq(false, ci, "No identity should be returned here");
-
- ci = await browser.contextualIdentities.get("firefox-container-1");
- browser.test.assertEq(false, ci, "We don't have any identity");
-
- let cis = await browser.contextualIdentities.query({});
- browser.test.assertEq(false, cis, "no containers, 0 containers");
-
- ci = await browser.contextualIdentities.create({name: "foobar", color: "red", icon: "icon"});
- browser.test.assertEq(false, ci, "We don't have any identity");
-
- ci = await browser.contextualIdentities.update("firefox-container-1", {name: "barfoo", color: "blue", icon: "icon icon"});
- browser.test.assertEq(false, ci, "We don't have any identity");
-
- ci = await browser.contextualIdentities.remove("firefox-container-1");
- browser.test.assertEq(false, ci, "We have an identity");
-
- browser.test.notifyPass("contextualIdentities");
- }
-
- let extension = ExtensionTestUtils.loadExtension({
- background: `(${backgroundScript})()`,
- manifest: {
- permissions: ["contextualIdentities"],
- },
- });
-
- Services.prefs.setBoolPref("privacy.userContext.enabled", false);
-
- await extension.startup();
- await extension.awaitFinish("contextualIdentities");
- await extension.unload();
-
- Services.prefs.clearUserPref("privacy.userContext.enabled");
-});
-
add_task(async function test_contextualIdentity_events() {
- async function backgroundScript() {
+ const CONTAINERS_PREF = "privacy.userContext.enabled";
+ async function background() {
function createOneTimeListener(type) {
return new Promise((resolve, reject) => {
try {
browser.test.assertTrue(type in browser.contextualIdentities, `Found API object browser.contextualIdentities.${type}`);
const listener = (change) => {
browser.test.assertTrue("contextualIdentity" in change, `Found identity in change`);
browser.contextualIdentities[type].removeListener(listener);
resolve(change);
@@ -108,33 +79,39 @@ add_task(async function test_contextualI
browser.test.assertTrue(!!ci, "We have an remove identity");
const onRemoveListenerResponse = await onRemovePromise;
assertExpected(onRemoveListenerResponse.contextualIdentity, Object.assign(updateContainerObj, {cookieStoreId}));
browser.test.notifyPass("contextualIdentities_events");
}
let extension = ExtensionTestUtils.loadExtension({
- background: `(${backgroundScript})()`,
+ background,
+ useAddonManager: "temporary",
manifest: {
+ applications: {
+ gecko: {id: "testing@thing.com"},
+ },
permissions: ["contextualIdentities"],
},
});
- Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+ Services.prefs.setBoolPref(CONTAINERS_PREF, true);
await extension.startup();
await extension.awaitFinish("contextualIdentities_events");
await extension.unload();
- Services.prefs.clearUserPref("privacy.userContext.enabled");
+ Services.prefs.clearUserPref(CONTAINERS_PREF);
});
add_task(async function test_contextualIdentity_with_permissions() {
- async function backgroundScript() {
+ const CONTAINERS_PREF = "privacy.userContext.enabled";
+ const initial = Services.prefs.getBoolPref(CONTAINERS_PREF);
+ async function background(ver) {
let ci = await browser.contextualIdentities.get("foobar");
browser.test.assertEq(null, ci, "No identity should be returned here");
ci = await browser.contextualIdentities.get("firefox-container-1");
browser.test.assertTrue(!!ci, "We have an identity");
browser.test.assertTrue("name" in ci, "We have an identity.name");
browser.test.assertTrue("color" in ci, "We have an identity.color");
browser.test.assertTrue("icon" in ci, "We have an identity.icon");
@@ -184,24 +161,79 @@ add_task(async function test_contextualI
browser.test.assertEq("blue", ci.color, "identity.color is correct");
browser.test.assertEq("icon icon", ci.icon, "identity.icon is correct");
cis = await browser.contextualIdentities.query({});
browser.test.assertEq(4, cis.length, "we are back to 4 identities");
browser.test.notifyPass("contextualIdentities");
}
+ function makeExtension(id) {
+ return ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+ background,
+ manifest: {
+ applications: {
+ gecko: {id},
+ },
+ permissions: ["contextualIdentities"],
+ },
+ });
+ }
- let extension = ExtensionTestUtils.loadExtension({
- background: `(${backgroundScript})()`,
- manifest: {
- permissions: ["contextualIdentities"],
- },
- });
-
- Services.prefs.setBoolPref("privacy.userContext.enabled", true);
-
+ let extension = makeExtension("containers-test@mozilla.org");
await extension.startup();
await extension.awaitFinish("contextualIdentities");
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled, whatever it's initial state");
await extension.unload();
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), initial, "Pref should now be initial state");
+
+ Services.prefs.clearUserPref(CONTAINERS_PREF);
+});
+
+add_task(async function test_contextualIdentity_extensions_enable_containers() {
+ const CONTAINERS_PREF = "privacy.userContext.enabled";
+ const initial = Services.prefs.getBoolPref(CONTAINERS_PREF);
+ async function background(ver) {
+ let ci = await browser.contextualIdentities.get("firefox-container-1");
+ browser.test.assertTrue(!!ci, "We have an identity");
- Services.prefs.clearUserPref("privacy.userContext.enabled");
+ browser.test.notifyPass("contextualIdentities");
+ }
+ function makeExtension(id) {
+ return ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+ background,
+ manifest: {
+ applications: {
+ gecko: {id},
+ },
+ permissions: ["contextualIdentities"],
+ },
+ });
+ }
+
+ let extension = makeExtension("containers-test@mozilla.org");
+ await extension.startup();
+ await extension.awaitFinish("contextualIdentities");
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled, whatever it's initial state");
+ await extension.unload();
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), initial, "Pref should now be initial state");
+
+ // Lets set containers explicitly to be on and test we keep it that way after removal
+ Services.prefs.setBoolPref(CONTAINERS_PREF, true);
+
+ let extension2 = makeExtension("containers-test-2@mozilla.org");
+ let extension3 = makeExtension("containers-test-3@mozilla.org");
+ await extension2.startup();
+ await extension2.awaitFinish("contextualIdentities");
+ await extension3.startup();
+ await extension3.awaitFinish("contextualIdentities");
+
+ // Flip the ordering to check it's still enabled
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 1");
+ await extension3.unload();
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 2");
+ await extension2.unload();
+ equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled 3");
+
+ Services.prefs.clearUserPref(CONTAINERS_PREF);
});