diff --git a/data/usercontext.css b/data/usercontext.css index 562a4ee..6c57db3 100644 --- a/data/usercontext.css +++ b/data/usercontext.css @@ -105,16 +105,23 @@ value, or chrome url path as an alternate selector mitiages this bug.*/ #userContext-indicator { height: 16px; list-style-image: none !important; + vertical-align: middle; width: 16px; } #userContext-label { - margin-inline-end: 5px; color: var(--identity-tab-color) !important; + margin-inline-end: 5px; + max-inline-size: 75px; + text-overflow: ellipsis; + white-space: nowrap; } #userContext-icons { -moz-box-align: center; + align-items: center; + display: flex; + max-inline-size: 120px; } .userContext-icon, diff --git a/docs/metrics.md b/docs/metrics.md index 50e9728..3a0bb86 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -169,6 +169,15 @@ of a `testpilottest` telemetry ping for each scenario. } ``` +* When a user encounters the disabled "move" feature because of incompatible add-ons + +```js + { + "uuid": , + "event": "incompatible-addons-detected" + } +``` + ### A Redshift schema for the payload: ```lua diff --git a/index.js b/index.js index 228edd7..915c442 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,12 @@ const DEFAULT_TAB = "about:newtab"; const SHOW_MENU_TIMEOUT = 100; const HIDE_MENU_TIMEOUT = 300; +const INCOMPATIBLE_ADDON_IDS = [ + "pulse@mozilla.com", + "snoozetabs@mozilla.com", + "jid1-NeEaf3sAHdKHPA@jetpack" // PageShot +]; + const IDENTITY_COLORS = [ { name: "blue", color: "#00a7e0" }, { name: "turquoise", color: "#01bdad" }, @@ -40,6 +46,7 @@ const PREFS = [ [ "privacy.usercontext.about_newtab_segregation.enabled", true ], ]; +const { AddonManager } = require("resource://gre/modules/AddonManager.jsm"); const { attachTo, detachFrom } = require("sdk/content/mod"); const { Cu } = require("chrome"); const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); @@ -105,6 +112,7 @@ const ContextualIdentityProxy = { const ContainerService = { _identitiesState: {}, _windowMap: new Map(), + _containerWasEnabled: false, init(installation) { // If we are just been installed, we must store some information for the @@ -139,8 +147,23 @@ const ContainerService = { identity.color); } } + + // Let's create the default containers in case there are none. + if (prefService.get("privacy.userContext.enabled") !== true && + ss.storage.savedConfiguration.preInstalledIdentities.length === 0) { + // Note: we have to create them in this way because there is no way to + // reuse the same ID and the localized strings. + ContextualIdentityService.create("Personal", "fingerprint", "blue"); + ContextualIdentityService.create("Work", "briefcase", "orange"); + ContextualIdentityService.create("Finance", "dollar", "green"); + ContextualIdentityService.create("Shopping", "cart", "pink"); + } } + // Let's see if containers were enabled before this addon. + this._containerWasEnabled = + ss.storage.savedConfiguration.prefs["privacy.userContext.enabled"]; + // Enabling preferences PREFS.forEach((pref) => { @@ -170,7 +193,8 @@ const ContainerService = { "removeIdentity", "updateIdentity", "getPreference", - "sendTelemetryPayload" + "sendTelemetryPayload", + "checkIncompatibleAddons" ]; // Map of identities. @@ -477,6 +501,21 @@ const ContainerService = { this._sendEvent(payload); }, + checkIncompatibleAddons() { + return new Promise(resolve => { + AddonManager.getAddonsByIDs(INCOMPATIBLE_ADDON_IDS, (addons) => { + addons = addons.filter((a) => a && a.isActive); + const incompatibleAddons = addons.length !== 0; + if (incompatibleAddons) { + this.sendTelemetryPayload({ + "event": "incompatible-addons-detected" + }); + } + resolve(incompatibleAddons); + }); + }); + }, + // Tabs management hideTabs(args) { @@ -673,6 +712,11 @@ const ContainerService = { return; } + this._remapTabsIfMissing(args.userContextId); + if (!this._isKnownContainer(args.userContextId)) { + return Promise.resolve(null); + } + this.sendTelemetryPayload({ "event": "move-tabs-to-window", "userContextId": args.userContextId, @@ -686,7 +730,8 @@ const ContainerService = { }); // Nothing to do - if (list.length === 0) { + if (list.length === 0 && + this._identitiesState[args.userContextId].hiddenTabs.length === 0) { resolve(null); return; } @@ -702,6 +747,13 @@ const ContainerService = { newBrowserWindow.gBrowser.adoptTab(viewFor(tab), pos++, false); } + // Let's show the hidden tabs. + for (let object of this._identitiesState[args.userContextId].hiddenTabs) { // eslint-disable-line prefer-const + newBrowserWindow.gBrowser.addTab(object.url || DEFAULT_TAB, { userContextId: args.userContextId }); + } + + this._identitiesState[args.userContextId].hiddenTabs = []; + // Let's close all the normal tab in the new window. In theory it // should be only the first tab, but maybe there are addons doing // crazy stuff. @@ -964,16 +1016,16 @@ const ContainerService = { for (let window of windows.browserWindows) { // eslint-disable-line prefer-const // Let's close all the container tabs. - // Note 1: we don't care if containers are supported but the current FF - // version. - // Note 2: We cannot use _closeTabs() because at this point tab.window is - // null. - for (let tab of window.tabs) { // eslint-disable-line prefer-const - if (this._getUserContextIdFromTab(tab)) { - tab.close(); - try { - SessionStore.forgetClosedTab(viewFor(window), 0); - } catch(e) {} // eslint-disable-line no-empty + // Note: We cannot use _closeTabs() because at this point tab.window is + // null. + if (!this._containerWasEnabled) { + for (let tab of window.tabs) { // eslint-disable-line prefer-const + if (this._getUserContextIdFromTab(tab)) { + tab.close(); + try { + SessionStore.forgetClosedTab(viewFor(window), 0); + } catch(e) {} // eslint-disable-line no-empty + } } } @@ -1101,14 +1153,16 @@ ContainerWindow.prototype = { }, _configurePlusButtonMenuElement(buttonElement) { - // Let's remove the tooltip because it can go over our panel. - this._tooltipCache.set(buttonElement, buttonElement.getAttribute("tooltip")); - buttonElement.setAttribute("tooltip", ""); - this._disableElement(buttonElement); + if (buttonElement) { + // Let's remove the tooltip because it can go over our panel. + this._tooltipCache.set(buttonElement, buttonElement.getAttribute("tooltip")); + buttonElement.setAttribute("tooltip", ""); + this._disableElement(buttonElement); - buttonElement.addEventListener("mouseover", this); - buttonElement.addEventListener("click", this); - buttonElement.addEventListener("mouseout", this); + buttonElement.addEventListener("mouseover", this); + buttonElement.addEventListener("click", this); + buttonElement.addEventListener("mouseout", this); + } }, _configurePlusButtonMenu() { @@ -1318,12 +1372,14 @@ ContainerWindow.prototype = { }, _shutDownPlusButtonMenuElement(buttonElement) { - this._shutdownElement(buttonElement); - buttonElement.setAttribute("tooltip", this._tooltipCache.get(buttonElement)); + if (buttonElement) { + this._shutdownElement(buttonElement); + buttonElement.setAttribute("tooltip", this._tooltipCache.get(buttonElement)); - buttonElement.removeEventListener("mouseover", this); - buttonElement.removeEventListener("click", this); - buttonElement.removeEventListener("mouseout", this); + buttonElement.removeEventListener("mouseover", this); + buttonElement.removeEventListener("click", this); + buttonElement.removeEventListener("mouseout", this); + } }, _shutdownPlusButtonMenu() { diff --git a/package.json b/package.json index d8abfac..450064d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "testpilot-containers", "title": "Containers Experiment", "description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.", - "version": "0.9.4", + "version": "0.9.5", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "bugs": { "url": "https://github.com/mozilla/testpilot-containers/issues" diff --git a/webextension/css/popup.css b/webextension/css/popup.css index 3b0498b..e2c8cd2 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -416,11 +416,24 @@ span ~ .panel-header-text { } /* Container info list */ +#container-info-name { + margin-inline-end: 0.5rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + #container-info-hideorshow { margin-block-start: 4px; } -.container-info-tab-row:not(.clickable) { +#container-info-movetabs-incompat { + font-size: 10px; + opacity: 0.3; +} + +.container-info-tab-row:not(.clickable), +.select-row:not(.clickable) { opacity: 0.3; } diff --git a/webextension/js/popup.js b/webextension/js/popup.js index b8f982c..c75985c 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -302,13 +302,36 @@ Logic.registerPanel(P_CONTAINER_INFO, { }); }); - document.querySelector("#container-info-movetabs").addEventListener("click", () => { - return browser.runtime.sendMessage({ - method: "moveTabsToWindow", - userContextId: Logic.currentIdentity().userContextId, - }).then(() => { - window.close(); - }); + // Check if the user has incompatible add-ons installed + browser.runtime.sendMessage({ + method: "checkIncompatibleAddons" + }).then(incompatible => { + const moveTabsEl = document.querySelector("#container-info-movetabs"); + if (incompatible) { + const fragment = document.createDocumentFragment(); + const incompatEl = document.createElement("div"); + + moveTabsEl.classList.remove("clickable"); + moveTabsEl.setAttribute("title", "Moving container tabs is incompatible with Pulse, PageShot, and SnoozeTabs."); + + fragment.appendChild(incompatEl); + incompatEl.setAttribute("id", "container-info-movetabs-incompat"); + incompatEl.innerText = "Incompatible with other Experiments."; + incompatEl.classList.add("container-info-tab-row"); + + moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling); + } else { + moveTabsEl.addEventListener("click", () => { + return browser.runtime.sendMessage({ + method: "moveTabsToWindow", + userContextId: Logic.currentIdentity().userContextId, + }).then(() => { + window.close(); + }); + }); + } + }).catch(() => { + throw new Error("Could not check for incompatible add-ons."); }); }, diff --git a/webextension/manifest.json b/webextension/manifest.json index da48e73..05d358c 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Containers Experiment", - "version": "0.9.4", + "version": "0.9.5", "description": "Containers works by isolating cookie jars using separate origin-attributes defined visually by colored ‘Container Tabs’. This add-on is a modified version of the containers feature for Firefox Test Pilot.", "icons": { diff --git a/webextension/popup.html b/webextension/popup.html index e87f0e0..fafc66d 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -99,7 +99,7 @@
Name - +
Choose a color