From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001
From: "Matt A. Tobin" <mattatobin@localhost.localdomain>
Date: Fri, 2 Feb 2018 04:16:08 -0500
Subject: Add m-esr52 at 52.6.0

---
 .../ContextualIdentityService.jsm                  | 344 +++++++++++++++++++++
 toolkit/components/contextualidentity/moz.build    |  11 +
 .../contextualidentity/tests/unit/test_basic.js    |  67 ++++
 .../contextualidentity/tests/unit/xpcshell.ini     |   3 +
 4 files changed, 425 insertions(+)
 create mode 100644 toolkit/components/contextualidentity/ContextualIdentityService.jsm
 create mode 100644 toolkit/components/contextualidentity/moz.build
 create mode 100644 toolkit/components/contextualidentity/tests/unit/test_basic.js
 create mode 100644 toolkit/components/contextualidentity/tests/unit/xpcshell.ini

(limited to 'toolkit/components/contextualidentity')

diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.jsm b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
new file mode 100644
index 000000000..6aae3673d
--- /dev/null
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.jsm
@@ -0,0 +1,344 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["ContextualIdentityService"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const DEFAULT_TAB_COLOR = "#909090";
+const SAVE_DELAY_MS = 1500;
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+  return Services.strings.createBundle("chrome://browser/locale/browser.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
+  return new TextDecoder();
+});
+
+XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
+  return new TextEncoder();
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+                                  "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");
+
+function _ContextualIdentityService(path) {
+  this.init(path);
+}
+
+_ContextualIdentityService.prototype = {
+  _defaultIdentities: [
+    { userContextId: 1,
+      public: true,
+      icon: "fingerprint",
+      color: "blue",
+      l10nID: "userContextPersonal.label",
+      accessKey: "userContextPersonal.accesskey",
+      telemetryId: 1,
+    },
+    { userContextId: 2,
+      public: true,
+      icon: "briefcase",
+      color: "orange",
+      l10nID: "userContextWork.label",
+      accessKey: "userContextWork.accesskey",
+      telemetryId: 2,
+    },
+    { userContextId: 3,
+      public: true,
+      icon: "dollar",
+      color: "green",
+      l10nID: "userContextBanking.label",
+      accessKey: "userContextBanking.accesskey",
+      telemetryId: 3,
+    },
+    { userContextId: 4,
+      public: true,
+      icon: "cart",
+      color: "pink",
+      l10nID: "userContextShopping.label",
+      accessKey: "userContextShopping.accesskey",
+      telemetryId: 4,
+    },
+    { userContextId: 5,
+      public: false,
+      icon: "",
+      color: "",
+      name: "userContextIdInternal.thumbnail",
+      accessKey: "" },
+  ],
+
+  _identities: null,
+  _openedIdentities: new Set(),
+  _lastUserContextId: 0,
+
+  _path: null,
+  _dataReady: false,
+
+  _saver: null,
+
+  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() {
+    OS.File.read(this._path).then(bytes => {
+      // If synchronous loading happened in the meantime, exit now.
+      if (this._dataReady) {
+        return;
+      }
+
+      try {
+        let data = JSON.parse(gTextDecoder.decode(bytes));
+        if (data.version == 1) {
+          this.resetDefault();
+        }
+        if (data.version != 2) {
+          dump("ERROR - ContextualIdentityService - Unknown version found in " + this._path + "\n");
+          this.loadError(null);
+          return;
+        }
+
+        this._identities = data.identities;
+        this._lastUserContextId = data.lastUserContextId;
+
+        this._dataReady = true;
+      } catch (error) {
+        this.loadError(error);
+      }
+    }, (error) => {
+      this.loadError(error);
+    });
+  },
+
+  resetDefault() {
+    this._identities = this._defaultIdentities;
+    this._lastUserContextId = this._defaultIdentities.length;
+
+    this._dataReady = true;
+
+    this.saveSoon();
+  },
+
+  loadError(error) {
+    if (error != null &&
+        !(error instanceof OS.File.Error && error.becauseNoSuchFile) &&
+        !(error instanceof Components.Exception &&
+          error.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
+      // Let's report the error.
+      Cu.reportError(error);
+    }
+
+    // If synchronous loading happened in the meantime, exit now.
+    if (this._dataReady) {
+      return;
+    }
+
+    this.resetDefault();
+  },
+
+  saveSoon() {
+    this._saver.arm();
+  },
+
+  save() {
+   let object = {
+     version: 2,
+     lastUserContextId: this._lastUserContextId,
+     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 && name) {
+      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;
+    }
+
+    Services.obs.notifyObservers(null, "clear-origin-attributes-data",
+                                 JSON.stringify({ userContextId }));
+
+    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"]
+                          .createInstance(Ci.nsIFileInputStream);
+      inputStream.init(new FileUtils.File(this._path),
+                       FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+      try {
+        let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+        let data = json.decodeFromStream(inputStream,
+                                         inputStream.available());
+        this._identities = data.identities;
+        this._lastUserContextId = data.lastUserContextId;
+
+        this._dataReady = true;
+      } finally {
+        inputStream.close();
+      }
+    } catch (error) {
+      this.loadError(error);
+      return;
+    }
+  },
+
+  getIdentities() {
+    this.ensureDataReady();
+    return Cu.cloneInto(this._identities.filter(info => info.public), {});
+  },
+
+  getPrivateIdentity(name) {
+    this.ensureDataReady();
+    return Cu.cloneInto(this._identities.find(info => !info.public && info.name == name), {});
+  },
+
+  getIdentityFromId(userContextId) {
+    this.ensureDataReady();
+    return Cu.cloneInto(this._identities.find(info => info.userContextId == userContextId &&
+                                              info.public), {});
+  },
+
+  getUserContextLabel(userContextId) {
+    let identity = this.getIdentityFromId(userContextId);
+    if (!identity || !identity.public) {
+      return "";
+    }
+
+    // We cannot localize the user-created identity names.
+    if (identity.name) {
+      return identity.name;
+    }
+
+    return gBrowserBundle.GetStringFromName(identity.l10nID);
+  },
+
+  setTabStyle(tab) {
+    if (!tab.hasAttribute("usercontextid")) {
+      return;
+    }
+
+    let userContextId = tab.getAttribute("usercontextid");
+    let identity = this.getIdentityFromId(userContextId);
+    tab.setAttribute("data-identity-color", identity ? identity.color : "");
+  },
+
+  countContainerTabs() {
+    let count = 0;
+    this._forEachContainerTab(function() { ++count; });
+    return count;
+  },
+
+  closeAllContainerTabs() {
+    this._forEachContainerTab(function(tab, tabbrowser) {
+      tabbrowser.removeTab(tab);
+    });
+  },
+
+  _forEachContainerTab(callback) {
+    let windowList = Services.wm.getEnumerator("navigator:browser");
+    while (windowList.hasMoreElements()) {
+      let win = windowList.getNext();
+
+      if (win.closed || !win.gBrowser) {
+	continue;
+      }
+
+      let tabbrowser = win.gBrowser;
+      for (let i = tabbrowser.tabContainer.childNodes.length - 1; i >= 0; --i) {
+        let tab = tabbrowser.tabContainer.childNodes[i];
+	if (tab.hasAttribute("usercontextid")) {
+	  callback(tab, tabbrowser);
+	}
+      }
+    }
+  },
+
+  telemetry(userContextId) {
+    let identity = this.getIdentityFromId(userContextId);
+
+    // Let's ignore unknown identities for now.
+    if (!identity || !identity.public) {
+      return;
+    }
+
+    if (!this._openedIdentities.has(userContextId)) {
+      this._openedIdentities.add(userContextId);
+      Services.telemetry.getHistogramById("UNIQUE_CONTAINERS_OPENED").add(1);
+    }
+
+    Services.telemetry.getHistogramById("TOTAL_CONTAINERS_OPENED").add(1);
+
+    if (identity.telemetryId) {
+      Services.telemetry.getHistogramById("CONTAINER_USED")
+                        .add(identity.telemetryId);
+    }
+  },
+
+  createNewInstanceForTesting(path) {
+    return new _ContextualIdentityService(path);
+  },
+};
+
+let path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
+this.ContextualIdentityService = new _ContextualIdentityService(path);
diff --git a/toolkit/components/contextualidentity/moz.build b/toolkit/components/contextualidentity/moz.build
new file mode 100644
index 000000000..9188421f9
--- /dev/null
+++ b/toolkit/components/contextualidentity/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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']
diff --git a/toolkit/components/contextualidentity/tests/unit/test_basic.js b/toolkit/components/contextualidentity/tests/unit/test_basic.js
new file mode 100644
index 000000000..4d17b9a26
--- /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");
+});
diff --git a/toolkit/components/contextualidentity/tests/unit/xpcshell.ini b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..b45ff2c30
--- /dev/null
+++ b/toolkit/components/contextualidentity/tests/unit/xpcshell.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_basic.js]
-- 
cgit v1.2.3