Bug 1287091 - part 4 - ContextualIdentityService create/remove/update, r=Gijs
☠☠ backed out by 50b8cc9b8e57 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Sat, 23 Jul 2016 08:58:23 +0200
changeset 346472 c01aeb081e2d201c27eba23327f125566c1cc29a
parent 346471 c5efa902cbe52cda80ef1db75322f8d0a9b31d18
child 346473 165e7b455b8ea84bd4dee186f754f6affa4c8cac
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/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/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]