Bug 1344519 - Add web extension events for containers onUpdated, onCreated and onRemoved r?aswan r?baku
MozReview-Commit-ID: 9Zxjc1J2CAt
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -399,16 +399,36 @@ getUserMedia.audioDevice.none = No Audio
getUserMedia.audioDevice.prompt = Microphone to use
getUserMedia.sharingCamera.message2 = Camera is on
getUserMedia.sharingMicrophone.message2 = Microphone is on
getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
getUserMedia.blockedCameraAccess = Camera has been blocked.
getUserMedia.blockedMicrophoneAccess = Microphone has been blocked.
getUserMedia.blockedCameraAndMicrophoneAccess = Camera and microphone have been blocked.
+# LOCALIZATION NOTE (userContextPersonal.label,
+# userContextWork.label,
+# userContextShopping.label,
+# userContextBanking.label,
+# userContextNone.label):
+# These strings specify the four predefined contexts included in support of the
+# Contextual Identity / Containers project. Each context is meant to represent
+# the context that the user is in when interacting with the site. Different
+# contexts will store cookies and other information from those sites in
+# different, isolated locations. You can enable the feature by typing
+# about:config in the URL bar and changing privacy.userContext.enabled to true.
+# Once enabled, you can open a new tab in a specific context by clicking
+# File > New Container Tab > (1 of 4 contexts). Once opened, you will see these
+# strings on the right-hand side of the URL bar.
+# In android this will be only exposed by web extensions
+userContextPersonal.label = Personal
+userContextWork.label = Work
+userContextBanking.label = Banking
+userContextShopping.label = Shopping
+
# LOCALIZATION NOTE (readerMode.toolbarTip):
# Tip shown to users the first time we hide the reader mode toolbar.
readerMode.toolbarTip=Tap the screen to show reader options
#Open in App
openInApp.pageAction = Open in App
openInApp.ok = OK
openInApp.cancel = Cancel
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -211,57 +211,73 @@ function _ContextualIdentityService(path
public: true,
icon,
color,
name
};
this._identities.push(identity);
this.saveSoon();
+ Services.obs.notifyObservers(this.getIdentityObserverOutput(identity),
+ "contextual-identity-created");
return Cu.cloneInto(identity, {});
},
update(userContextId, name, icon, color) {
this.ensureDataReady();
let identity = this._identities.find(identity => identity.userContextId == userContextId &&
identity.public);
if (identity && name) {
identity.name = name;
identity.color = color;
identity.icon = icon;
delete identity.l10nID;
delete identity.accessKey;
- Services.obs.notifyObservers(null, "contextual-identity-updated", userContextId);
this.saveSoon();
+ Services.obs.notifyObservers(this.getIdentityObserverOutput(identity),
+ "contextual-identity-updated");
}
return !!identity;
},
remove(userContextId) {
this.ensureDataReady();
let index = this._identities.findIndex(i => i.userContextId == userContextId && i.public);
if (index == -1) {
return false;
}
Services.obs.notifyObservers(null, "clear-origin-attributes-data",
JSON.stringify({ userContextId }));
+ let deletedOutput = this.getIdentityObserverOutput(this.getPublicIdentityFromId(userContextId));
this._identities.splice(index, 1);
this._openedIdentities.delete(userContextId);
this.saveSoon();
+ Services.obs.notifyObservers(deletedOutput, "contextual-identity-deleted");
return true;
},
+ getIdentityObserverOutput(identity) {
+ let wrappedJSObject = {
+ name: this.getUserContextLabel(identity.userContextId),
+ icon: identity.icon,
+ color: identity.color,
+ userContextId: identity.userContextId,
+ };
+
+ return {wrappedJSObject};
+ },
+
ensureDataReady() {
if (this._dataReady) {
return;
}
try {
// This reads the file and automatically detects the UTF-8 encoding.
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
--- a/toolkit/components/contextualidentity/tests/unit/test_basic.js
+++ b/toolkit/components/contextualidentity/tests/unit/test_basic.js
@@ -1,25 +1,26 @@
"use strict";
-do_get_profile();
+const profileDir = do_get_profile();
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
-const TEST_STORE_FILE_NAME = "test-containers.json";
+const TEST_STORE_FILE_PATH = OS.Path.join(profileDir.path, "test-containers.json");
let cis;
// Basic tests
add_task(function() {
ok(!!ContextualIdentityService, "ContextualIdentityService exists");
- cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_NAME);
+ cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_PATH);
ok(!!cis, "We have our instance of ContextualIdentityService");
equal(cis.getPublicIdentities().length, 4, "By default, 4 containers.");
equal(cis.getPublicIdentityFromId(0), null, "No identity with id 0");
ok(!!cis.getPublicIdentityFromId(1), "Identity 1 exists");
ok(!!cis.getPublicIdentityFromId(2), "Identity 2 exists");
ok(!!cis.getPublicIdentityFromId(3), "Identity 3 exists");
--- a/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
+++ b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
@@ -1,3 +1,4 @@
[DEFAULT]
+firefox-appdir = browser
[test_basic.js]
--- a/toolkit/components/extensions/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/ext-contextualIdentities.js
@@ -14,16 +14,28 @@ const convertIdentity = identity => {
icon: identity.icon,
color: identity.color,
cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
};
return result;
};
+const convertIdentityFromObserver = wrappedIdentity => {
+ let identity = wrappedIdentity.wrappedJSObject;
+ let result = {
+ name: identity.name,
+ icon: identity.icon,
+ color: identity.color,
+ cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
+ };
+
+ return result;
+};
+
this.contextualIdentities = class extends ExtensionAPI {
getAPI(context) {
let self = {
contextualIdentities: {
get(cookieStoreId) {
if (!containersEnabled) {
return Promise.resolve(false);
}
@@ -121,14 +133,48 @@ this.contextualIdentities = class extend
let convertedIdentity = convertIdentity(identity);
if (!ContextualIdentityService.remove(identity.userContextId)) {
return Promise.resolve(null);
}
return Promise.resolve(convertedIdentity);
},
+
+ onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
+ let observer = (subject, topic) => {
+ fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+ };
+
+ Services.obs.addObserver(observer, "contextual-identity-created");
+ return () => {
+ Services.obs.removeObserver(observer, "contextual-identity-created");
+ };
+ }).api(),
+
+ onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => {
+ let observer = (subject, topic) => {
+ fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+ };
+
+ Services.obs.addObserver(observer, "contextual-identity-updated");
+ return () => {
+ Services.obs.removeObserver(observer, "contextual-identity-updated");
+ };
+ }).api(),
+
+ onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => {
+ let observer = (subject, topic) => {
+ fire.async({contextualIdentity: convertIdentityFromObserver(subject)});
+ };
+
+ Services.obs.addObserver(observer, "contextual-identity-deleted");
+ return () => {
+ Services.obs.removeObserver(observer, "contextual-identity-deleted");
+ };
+ }).api(),
+
},
};
return self;
}
};
--- a/toolkit/components/extensions/schemas/contextual_identities.json
+++ b/toolkit/components/extensions/schemas/contextual_identities.json
@@ -113,11 +113,55 @@
"parameters": [
{
"type": "string",
"name": "cookieStoreId",
"description": "The ID of the contextual identity cookie store. "
}
]
}
+ ],
+ "events": [
+ {
+ "name": "onUpdated",
+ "type": "function",
+ "description": "Fired when a container is updated.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "changeInfo",
+ "properties": {
+ "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been updated"}
+ }
+ }
+ ]
+ },
+ {
+ "name": "onCreated",
+ "type": "function",
+ "description": "Fired when a new container is created.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "changeInfo",
+ "properties": {
+ "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been created"}
+ }
+ }
+ ]
+ },
+ {
+ "name": "onRemoved",
+ "type": "function",
+ "description": "Fired when a container is removed.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "changeInfo",
+ "properties": {
+ "contextualIdentity": {"$ref": "ContextualIdentity", "description": "Contextual identity that has been removed"}
+ }
+ }
+ ]
+ }
]
}
]
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -56,16 +56,83 @@ add_task(async function test_contextualI
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() {
+ 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);
+ };
+ browser.contextualIdentities[type].addListener(listener);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }
+
+ function assertExpected(expected, container) {
+ for (let key of Object.keys(container)) {
+ browser.test.assertTrue(key in expected, `found property ${key}`);
+ browser.test.assertEq(expected[key], container[key], `property value for ${key} is correct`);
+ }
+ browser.test.assertEq(Object.keys(expected).length, Object.keys(container).length, "all expected properties found");
+ }
+
+ let onCreatePromise = createOneTimeListener("onCreated");
+
+ let containerObj = {name: "foobar", color: "red", icon: "icon"};
+ let ci = await browser.contextualIdentities.create(containerObj);
+ browser.test.assertTrue(!!ci, "We have an identity");
+ const onCreateListenerResponse = await onCreatePromise;
+ const cookieStoreId = ci.cookieStoreId;
+ assertExpected(onCreateListenerResponse.contextualIdentity, Object.assign(containerObj, {cookieStoreId}));
+
+ let onUpdatedPromise = createOneTimeListener("onUpdated");
+ let updateContainerObj = {name: "testing", color: "blue", icon: "thing"};
+ ci = await browser.contextualIdentities.update(cookieStoreId, updateContainerObj);
+ browser.test.assertTrue(!!ci, "We have an update identity");
+ const onUpdatedListenerResponse = await onUpdatedPromise;
+ assertExpected(onUpdatedListenerResponse.contextualIdentity, Object.assign(updateContainerObj, {cookieStoreId}));
+
+ let onRemovePromise = createOneTimeListener("onRemoved");
+ ci = await browser.contextualIdentities.remove(updateContainerObj.cookieStoreId);
+ 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})()`,
+ manifest: {
+ permissions: ["contextualIdentities"],
+ },
+ });
+
+ Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+
+ await extension.startup();
+ await extension.awaitFinish("contextualIdentities_events");
+ await extension.unload();
+
+ Services.prefs.clearUserPref("privacy.userContext.enabled");
+});
+
add_task(async function test_contextualIdentity_with_permissions() {
async function backgroundScript() {
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");