Bug 1287091 - part 4 - ContextualIdentityService create/remove/update, r=Gijs
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 25 Jul 2016 09:25:56 +0200
changeset 346502 11a988cd506aab15d8b3b855bf526e33f87cc75b
parent 346501 57942a8e13b20072a3ee73ff91fc514d79f2a522
child 346503 f577fea911602ccf974bc900ca55e936a43186d2
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
@@ -1130,17 +1130,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]