Bug 1287091 - part 4 - ContextualIdentityService create/remove/update, r=Gijs
☠☠ backed out by 820961ff0369 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 22 Jul 2016 16:19:50 +0200
changeset 346404 edad0174cb120f85438a533e8da67af96c29e96d
parent 346403 7ed07b35bad6e2b5571f24213787ab08237d88cc
child 346405 c57fd3af416e73fbfabcd6e7e985feedf6979504
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1287091
milestone50.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 1287091 - part 4 - ContextualIdentityService create/remove/update, r=Gijs
browser/base/content/utilityOverlay.js
browser/components/customizableui/CustomizableWidgets.jsm
toolkit/components/contextualidentity/ContextualIdentityService.jsm
toolkit/components/contextualidentity/moz.build
toolkit/components/contextualidentity/tests/unit/test_basic.js
toolkit/components/contextualidentity/tests/unit/xpcshell.ini
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -440,18 +440,22 @@ function createUserContextMenu(event, ad
 
   ContextualIdentityService.getIdentities().forEach(identity => {
     if (identity.userContextId == excludeUserContextId) {
       return;
     }
 
     let menuitem = document.createElement("menuitem");
     menuitem.setAttribute("usercontextid", identity.userContextId);
-    menuitem.setAttribute("label", bundle.getString(identity.label));
-    menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
+
+    if (identity.accessKey) {
+      menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    }
+
     menuitem.classList.add("menuitem-iconic");
 
     if (addCommandAttribute) {
       menuitem.setAttribute("command", "Browser:NewUserContextTab");
     }
 
     menuitem.setAttribute("image", identity.icon);
 
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1129,17 +1129,17 @@ const CustomizableWidgets = [
       while (items.firstChild) {
         items.firstChild.remove();
       }
 
       let fragment = doc.createDocumentFragment();
 
       ContextualIdentityService.getIdentities().forEach(identity => {
         let bundle = doc.getElementById("bundle_browser");
-        let label = bundle.getString(identity.label);
+        let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
 
         let item = doc.createElementNS(kNSXUL, "toolbarbutton");
         item.setAttribute("label", label);
         item.setAttribute("usercontextid", identity.userContextId);
         item.setAttribute("class", "subviewbutton");
         item.setAttribute("image", identity.icon);
 
         fragment.appendChild(item);
--- a/toolkit/components/contextualidentity/ContextualIdentityService.jsm
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -28,70 +28,73 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
-this.ContextualIdentityService = {
+function _ContextualIdentityService(path) {
+  this.init(path);
+}
+
+_ContextualIdentityService.prototype = {
   _defaultIdentities: [
     { userContextId: 1,
       public: true,
       icon: "chrome://browser/skin/usercontext/personal.svg",
       color: "#00a7e0",
-      label: "userContextPersonal.label",
+      l10nID: "userContextPersonal.label",
       accessKey: "userContextPersonal.accesskey",
       telemetryId: 1,
     },
     { userContextId: 2,
       public: true,
       icon: "chrome://browser/skin/usercontext/work.svg",
       color: "#f89c24",
-      label: "userContextWork.label",
+      l10nID: "userContextWork.label",
       accessKey: "userContextWork.accesskey",
       telemetryId: 2,
     },
     { userContextId: 3,
       public: true,
       icon: "chrome://browser/skin/usercontext/banking.svg",
       color: "#7dc14c",
-      label: "userContextBanking.label",
+      l10nID: "userContextBanking.label",
       accessKey: "userContextBanking.accesskey",
       telemetryId: 3,
     },
     { userContextId: 4,
       public: true,
       icon: "chrome://browser/skin/usercontext/shopping.svg",
       color: "#ee5195",
-      label: "userContextShopping.label",
+      l10nID: "userContextShopping.label",
       accessKey: "userContextShopping.accesskey",
       telemetryId: 4,
     },
     { userContextId: 5,
       public: false,
       icon: "",
       color: "",
-      label: "userContextIdInternal.thumbnail",
+      name: "userContextIdInternal.thumbnail",
       accessKey: "" },
   ],
 
   _identities: null,
   _openedIdentities: new Set(),
   _lastUserContextId: 0,
 
   _path: null,
   _dataReady: false,
 
   _saver: null,
 
-  init() {
-    this._path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
-
+  init(path) {
+    this._path = path;
     this._saver = new DeferredTask(() => this.save(), SAVE_DELAY_MS);
     AsyncShutdown.profileBeforeChange.addBlocker("ContextualIdentityService: writing data",
                                                  () => this._saver.finalize());
 
     this.load();
   },
 
   load() {
@@ -154,16 +157,59 @@ this.ContextualIdentityService = {
      identities: this._identities
    };
 
    let bytes = gTextEncoder.encode(JSON.stringify(object));
    return OS.File.writeAtomic(this._path, bytes,
                               { tmpPath: this._path + ".tmp" });
   },
 
+  create(name, icon, color) {
+    let identity = {
+      userContextId: ++this._lastUserContextId,
+      public: true,
+      icon,
+      color,
+      name
+    };
+
+    this._identities.push(identity);
+    this.saveSoon();
+
+    return Cu.cloneInto(identity, {});
+  },
+
+  update(userContextId, name, icon, color) {
+    let identity = this._identities.find(identity => identity.userContextId == userContextId &&
+                                         identity.public);
+    if (identity) {
+      identity.name = name;
+      identity.color = color;
+      identity.icon = icon;
+      delete identity.l10nID;
+      delete identity.accessKey;
+      this.saveSoon();
+    }
+
+    return !!identity;
+  },
+
+  remove(userContextId) {
+    let index = this._identities.findIndex(i => i.userContextId == userContextId && i.public);
+    if (index == -1) {
+      return false;
+    }
+
+    this._identities.splice(index, 1);
+    this._openedIdentities.delete(userContextId);
+    this.saveSoon();
+
+    return true;
+  },
+
   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"]
@@ -181,35 +227,42 @@ this.ContextualIdentityService = {
     } catch (error) {
       this.loadError(error);
       return;
     }
   },
 
   getIdentities() {
     this.ensureDataReady();
-    return this._identities.filter(info => info.public);
+    return Cu.cloneInto(this._identities.filter(info => info.public), {});
   },
 
-  getPrivateIdentity(label) {
+  getPrivateIdentity(name) {
     this.ensureDataReady();
-    return this._identities.find(info => !info.public && info.label == label);
+    return Cu.cloneInto(this._identities.find(info => !info.public && info.name == name), {});
   },
 
   getIdentityFromId(userContextId) {
     this.ensureDataReady();
-    return this._identities.find(info => info.userContextId == userContextId);
+    return Cu.cloneInto(this._identities.find(info => info.userContextId == userContextId &&
+                                              info.public), {});
   },
 
   getUserContextLabel(userContextId) {
     let identity = this.getIdentityFromId(userContextId);
     if (!identity.public) {
       return "";
     }
-    return gBrowserBundle.GetStringFromName(identity.label);
+
+    // We cannot localize the user-created identity names.
+    if (identity.name) {
+      return identity.name;
+    }
+
+    return gBrowserBundle.GetStringFromName(identity.l10nID);
   },
 
   setTabStyle(tab) {
     // inline style is only a temporary fix for some bad performances related
     // to the use of CSS vars. This code will be removed in bug 1278177.
     if (!tab.hasAttribute("usercontextid")) {
       tab.style.removeProperty("background-image");
       tab.style.removeProperty("background-size");
@@ -241,11 +294,16 @@ this.ContextualIdentityService = {
 
     Services.telemetry.getHistogramById("TOTAL_CONTAINERS_OPENED").add(1);
 
     if (identity.telemetryId) {
       Services.telemetry.getHistogramById("CONTAINER_USED")
                         .add(identity.telemetryId);
     }
   },
-}
 
-ContextualIdentityService.init();
+  createNewInstanceForTesting(path) {
+    return new _ContextualIdentityService(path);
+  },
+};
+
+let path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
+this.ContextualIdentityService = new _ContextualIdentityService(path);
--- a/toolkit/components/contextualidentity/moz.build
+++ b/toolkit/components/contextualidentity/moz.build
@@ -2,8 +2,10 @@
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_JS_MODULES += [
     'ContextualIdentityService.jsm',
 ]
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contextualidentity/tests/unit/test_basic.js
@@ -0,0 +1,67 @@
+"use strict";
+
+do_get_profile();
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+const TEST_STORE_FILE_NAME = "test-containers.json";
+
+let cis;
+
+// Basic tests
+add_task(function() {
+  ok(!!ContextualIdentityService, "ContextualIdentityService exists");
+
+  cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_NAME);
+  ok(!!cis, "We have our instance of ContextualIdentityService");
+
+  equal(cis.getIdentities().length, 4, "By default, 4 containers.");
+  equal(cis.getIdentityFromId(0), null, "No identity with id 0");
+
+  ok(!!cis.getIdentityFromId(1), "Identity 1 exists");
+  ok(!!cis.getIdentityFromId(2), "Identity 2 exists");
+  ok(!!cis.getIdentityFromId(3), "Identity 3 exists");
+  ok(!!cis.getIdentityFromId(4), "Identity 4 exists");
+});
+
+// Create a new identity
+add_task(function() {
+  equal(cis.getIdentities().length, 4, "By default, 4 containers.");
+
+  let identity = cis.create("New Container", "Icon", "Color");
+  ok(!!identity, "New container created");
+  equal(identity.name, "New Container", "Name matches");
+  equal(identity.icon, "Icon", "Icon matches");
+  equal(identity.color, "Color", "Color matches");
+
+  equal(cis.getIdentities().length, 5, "Expected 5 containers.");
+
+  ok(!!cis.getIdentityFromId(identity.userContextId), "Identity exists");
+  equal(cis.getIdentityFromId(identity.userContextId).name, "New Container", "Identity name is OK");
+  equal(cis.getIdentityFromId(identity.userContextId).icon, "Icon", "Identity icon is OK");
+  equal(cis.getIdentityFromId(identity.userContextId).color, "Color", "Identity color is OK");
+  equal(cis.getUserContextLabel(identity.userContextId), "New Container", "Identity label is OK");
+
+  // Remove an identity
+  equal(cis.remove(-1), false, "cis.remove() returns false if identity doesn't exist.");
+  equal(cis.remove(1), true, "cis.remove() returns true if identity exists.");
+
+  equal(cis.getIdentities().length, 4, "Expected 4 containers.");
+});
+
+// Update an identity
+add_task(function() {
+  ok(!!cis.getIdentityFromId(2), "Identity 2 exists");
+
+  equal(cis.update(-1, "Container", "Icon", "Color"), false, "Update returns false if the identity doesn't exist");
+
+  equal(cis.update(2, "Container", "Icon", "Color"), true, "Update returns true if everything is OK");
+
+  ok(!!cis.getIdentityFromId(2), "Identity exists");
+  equal(cis.getIdentityFromId(2).name, "Container", "Identity name is OK");
+  equal(cis.getIdentityFromId(2).icon, "Icon", "Identity icon is OK");
+  equal(cis.getIdentityFromId(2).color, "Color", "Identity color is OK");
+  equal(cis.getUserContextLabel(2), "Container", "Identity label is OK");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_basic.js]