Bug 1344519 - Add web extension events for containers onUpdated, onCreated and onRemoved r=aswan,baku
☠☠ backed out by a03f5a66ca83 ☠ ☠
authorJonathan Kingston <jkt@mozilla.com>
Sun, 14 May 2017 00:39:32 +0100
changeset 420806 59765ae0aee3463c30018ff329527f6eb7ff3dad
parent 420805 2c6f632e13263b78cae4e6822acf4e48e6a054c2
child 420807 806ed8cd2c1bed5867ced8d1d57e0773bf106ee1
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, baku
bugs1344519
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1344519 - Add web extension events for containers onUpdated, onCreated and onRemoved r=aswan,baku MozReview-Commit-ID: 9Zxjc1J2CAt
toolkit/components/contextualidentity/ContextualIdentityService.jsm
toolkit/components/extensions/ext-contextualIdentities.js
toolkit/components/extensions/schemas/contextual_identities.json
toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
--- 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/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");