From e4b6b6cb7fa481342339e491f4fa251d0f49d9c3 Mon Sep 17 00:00:00 2001 From: baku Date: Fri, 20 Jan 2017 10:29:50 +0100 Subject: [PATCH 1/5] Plus panel --- data/filters.svg | 9 ++ data/{chrome.css => usercontext.css} | 122 ++++++++++++++-- index.js | 204 ++++++++++++++++++++++----- 3 files changed, 293 insertions(+), 42 deletions(-) create mode 100644 data/filters.svg rename data/{chrome.css => usercontext.css} (61%) diff --git a/data/filters.svg b/data/filters.svg new file mode 100644 index 0000000..6119d59 --- /dev/null +++ b/data/filters.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/data/chrome.css b/data/usercontext.css similarity index 61% rename from data/chrome.css rename to data/usercontext.css index 9785ce9..929f66f 100644 --- a/data/chrome.css +++ b/data/usercontext.css @@ -1,13 +1,3 @@ -/* containers experiment */ -.tabbrowser-tab[usercontextid] { - background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important; - background-size: var(--identity-stroke-background-size) !important; - background-repeat: no-repeat !important; - background-position: 0 29px !important; - --identity-stroke-background-size: auto 2px; -} - -/* set defaults for ids as currently we don't set identity color */ [usercontextid="1"], [data-identity-color="blue"] { --identity-tab-color: #37adff; @@ -52,6 +42,73 @@ --identity-icon-color: #af51f5; } +[data-identity-icon="fingerprint"] { + /*--identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#fingerprint"); +} + +[data-identity-icon="briefcase"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#briefcase"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#briefcase"); +} + +[data-identity-icon="dollar"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#dollar"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#dollar"); +} + +[data-identity-icon="cart"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#cart"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#cart"); +} + +[data-identity-icon="circle"] { + /* --identity-icon: url("chrome://browser/content/usercontext.svg#circle"); */ + --identity-icon: url("resource://testpilot-containers/data/usercontext.svg#circle"); +} + +#userContext-indicator { + height: 16px; + width: 16px; +} + +#userContext-label { + margin-inline-end: 3px; + color: var(--identity-tab-color); +} + +#userContext-icons { + -moz-box-align: center; +} + +.tabbrowser-tab[usercontextid] { + background-image: linear-gradient(to right, transparent 20%, var(--identity-tab-color) 30%, var(--identity-tab-color) 70%, transparent 80%); + background-size: auto 2px; + background-repeat: no-repeat; +} + +.userContext-icon, +.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon, +.subviewbutton[usercontextid] > .toolbarbutton-icon, +#userContext-indicator { + background-image: var(--identity-icon); + filter: url(/img/filters.svg#fill); + filter: url(resource://testpilot-containers/data/filters.svg#fill); + fill: var(--identity-icon-color); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +/* containers experiment */ +.tabbrowser-tab[usercontextid] { + background-image: linear-gradient(to right, var(--identity-tab-color) 0%, var(--identity-tab-color) 100%) !important; + background-size: var(--identity-stroke-background-size) !important; + background-repeat: no-repeat !important; + background-position: 0 29px !important; + --identity-stroke-background-size: auto 2px; +} + .tabbrowser-tab[usercontextid] .tab-background-start:not([selected="true"]) { background-image: linear-gradient(to left, var(--identity-tab-color) 0%, var(--identity-tab-color) 50%, transparent 50%, transparent 100%); background-position: 0 28px; @@ -114,3 +171,48 @@ background-repeat: repeat-x; } /* end containers experiment */ + +.tabs-newtab-button .toolbarbutton-icon[type="menu"] { + margin-inline-end: 0; +} + +.tabs-newtab-button .toolbarbutton-menu-dropmarker { + display: none; +} + +#new-tab-overlay { + visibility: visible; + block-size: 200px; + inline-size: auto; + display: block; + background: transparent; + position: absolute; + -moz-appearance: none; + offset-block-start: 29px; +} +#new-tab-overlay[hidden=true] { + display: none; +} + +#new-tab-overlay menuitem { + background: white; + margin-block-end: 12px; + border-radius: 20px; + -moz-appearance: none; + color: #4b4b4b; + padding: 6px; + font-size: 1.2rem; + box-shadow: 3px 7px 7px #0006; + --icon-size: 26px; + /* Limited width to 8chars roughly */ + inline-size: calc(calc(8*0.68em) + var(--icon-size) + 3px); +} + +#new-tab-overlay .menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon { + block-height: var(--icon-size); + block-width: var(--icon-size); +} + +.menuitem-iconic[data-usercontextid] > .menu-iconic-left { + visibility: visible; +} diff --git a/index.js b/index.js index 37c8889..6dd87d4 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,9 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +const HIDE_MENU_TIMEOUT = 1000; +const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"]; + const { attachTo } = require("sdk/content/mod"); const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); const { getFavicon } = require("sdk/places/favicon"); @@ -16,10 +19,12 @@ const webExtension = require("sdk/webextension"); const windows = require("sdk/windows"); const windowUtils = require("sdk/window/utils"); -const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"]; +// ---------------------------------------------------------------------------- +// ContainerService const ContainerService = { _identitiesState: {}, + _windowMap: {}, init() { // Enabling preferences @@ -76,6 +81,7 @@ const ContainerService = { if (userContextId) { ++this._identitiesState[userContextId].openTabs; } + this._hideAllPanels(); }); tabs.on("close", tab => { @@ -83,6 +89,11 @@ const ContainerService = { if (userContextId && this._identitiesState[userContextId].openTabs) { --this._identitiesState[userContextId].openTabs; } + this._hideAllPanels(); + }); + + tabs.on("activate", () => { + this._hideAllPanels(); }); // Modify CSS and other stuff for each window. @@ -95,6 +106,10 @@ const ContainerService = { this.configureWindow(viewFor(window)); }); + windows.browserWindows.on("close", window => { + this.closeWindow(viewFor(window)); + }); + // WebExtension startup webExtension.startup().then(api => { @@ -464,41 +479,166 @@ const ContainerService = { // Styling the window configureWindow(window) { - const tabsElement = window.document.getElementById("tabbrowser-tabs"); - const button = window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); - - while (button.firstChild) { - button.removeChild(button.firstChild); + const id = windowUtils.getInnerId(window); + if (!(id in this._windowMap)) { + this._windowMap[id] = new ContainerWindow(window); } - button.setAttribute("type", "menu"); - const popup = window.document.createElementNS(XUL_NS, "menupopup"); + this._windowMap[id].configure(); + }, - popup.setAttribute("anonid", "newtab-popup"); - popup.className = "new-tab-popup"; - popup.setAttribute("position", "after_end"); + closeWindow(window) { + const id = windowUtils.getInnerId(window); + delete this._windowMap[id]; + }, - ContextualIdentityService.getIdentities().forEach(identity => { - identity = this._convert(identity); - - const menuItem = window.document.createElementNS(XUL_NS, "menuitem"); - menuItem.setAttribute("class", "menuitem-iconic"); - menuItem.setAttribute("label", identity.name); - menuItem.setAttribute("image", self.data.url("usercontext.svg") + "#" + identity.image); - - menuItem.addEventListener("command", (event) => { - this.openTab({userContextId: identity.userContextId}); - event.stopPropagation(); - }); - - popup.appendChild(menuItem); - }); - - button.appendChild(popup); - const style = Style({ uri: self.data.url("chrome.css") }); - - attachTo(style, viewFor(window)); - } + _hideAllPanels() { + for (let id in this._windowMap) { // eslint-disable-line prefer-const + this._windowMap[id].hidePanel(); + } + }, }; +// ---------------------------------------------------------------------------- +// ContainerWindow + +// This object is used to configure a single window. +function ContainerWindow(window) { + this._init(window); +} + +ContainerWindow.prototype = { + _window: null, + _panelElement: null, + _timeoutId: 0, + + _init(window) { + this._window = window; + const style = Style({ uri: self.data.url("usercontext.css") }); + attachTo(style, this._window); + }, + + configure() { + const tabsElement = this._window.document.getElementById("tabbrowser-tabs"); + + const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); + + // Let's remove the tooltip because it can go over our panel. + button.setAttribute("tooltip", ""); + + // Let's remove all the previous panels. + if (this._panelElement) { + this._panelElement.remove(); + } + + this._panelElement = this._window.document.createElementNS(XUL_NS, "panel"); + this._panelElement.setAttribute("id", "new-tab-overlay"); + button.after(this._panelElement); + this._panelElement.hidden = true; + + this._repositionPopup(); + + ContainerService.queryIdentities().then(identities => { + identities.forEach(identity => { + const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem"); + this._panelElement.appendChild(menuItemElement); + menuItemElement.className = "menuitem-iconic"; + menuItemElement.setAttribute("label", identity.name); + menuItemElement.setAttribute("data-usercontextid", identity.userContextId); + menuItemElement.setAttribute("data-identity-icon", identity.image); + menuItemElement.setAttribute("data-identity-color", identity.color); + + menuItemElement.addEventListener("command", e => { + ContainerService.openTab({userContextId: identity.userContextId}); + e.stopPropagation(); + }); + + //Command isn't working probably because I'm in a panel + menuItemElement.addEventListener("click", e => { + ContainerService.openTab({userContextId: identity.userContextId}); + e.stopPropagation(); + }); + + menuItemElement.addEventListener("mouseover", () => { + this._cleanTimeout(); + }); + + menuItemElement.addEventListener("mouseout", () => { + this._createTimeout(); + }); + + this._panelElement.appendChild(menuItemElement); + }); + }).catch(() => { + this.hidePanel(); + }); + + button.addEventListener("click", () => { + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseover", () => { + this._repositionPopup(); + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseout", () => { + this._createTimeout(); + }); + + this._panelElement.addEventListener("mouseout", (e) => { + if (e.target !== this._panelElement) { + this._createTimeout(); + return; + } + this._repositionPopup(); + }); + }, + + // This function puts the popup in the correct place. + _repositionPopup() { + const tabsElement = this._window.document.getElementById("tabbrowser-tabs"); + const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); + + const size = button.getBoxQuads()[0]; + const innerWindow = tabsElement.getBoxQuads()[0]; + const panelElementWidth = 200; + + // 1/4th of the way past the left hand side of the new tab button + // This seems to line up nicely with the left of the + + const offset = ((size.p3.x - size.p4.x) / 4); + let left = size.p4.x + offset; + if (left + panelElementWidth > innerWindow.p2.x) { + left -= panelElementWidth - offset; + } + this._panelElement.style.left = left + "px"; + this._panelElement.style.top = size.p4.y + "px"; + }, + + // This timer is used to hide the panel auto-magically if it's not used in + // the following X seconds. This is need to avoid the leaking of the panel + // when the mouse goes out of of the 'plus' button. + _createTimeout() { + this._cleanTimeout(); + this._timeoutId = this._window.setTimeout(() => { + this.hidePanel(); + this._timeoutId = 0; + }, HIDE_MENU_TIMEOUT); + }, + + _cleanTimeout() { + if (this._timeoutId) { + this._window.clearTimeout(this._timeoutId); + this._timeoutId = 0; + } + }, + + hidePanel() { + this._cleanTimeout(); + this._panelElement.hidden = true; + }, +}; + +// ---------------------------------------------------------------------------- +// Let's start :) ContainerService.init(); From 3db007a3eabd3a1685dbf8d3fcba6437f226ac78 Mon Sep 17 00:00:00 2001 From: baku Date: Sat, 21 Jan 2017 22:11:35 +0100 Subject: [PATCH 2/5] Issue #102 - Remove/Create/Update propagation in the '+' menu (no color/name in the awesomebar yet) --- index.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 6dd87d4..af9a3dd 100644 --- a/index.js +++ b/index.js @@ -98,9 +98,7 @@ const ContainerService = { // Modify CSS and other stuff for each window. - for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - this.configureWindow(viewFor(window)); - } + this.configureWindows(); windows.browserWindows.on("open", window => { this.configureWindow(viewFor(window)); @@ -441,6 +439,8 @@ const ContainerService = { openTabs: 0 }; + this._refreshNeeded(); + return Promise.resolve(this._convert(identity)); }, @@ -457,11 +457,14 @@ const ContainerService = { } // FIXME: icon and color conversion based on FF version. - // FIXME: color/name update propagation - return Promise.resolve(ContextualIdentityService.update(args.userContextId, - identity.name, - identity.icon, - identity.color)); + + const updated = ContextualIdentityService.update(args.userContextId, + identity.name, + identity.icon, + identity.color); + + this._refreshNeeded(); + return Promise.resolve(updated); }, removeIdentity(args) { @@ -473,11 +476,20 @@ const ContainerService = { tab.close(); }); - return Promise.resolve(ContextualIdentityService.remove(args.userContextId)); + const removed = ContextualIdentityService.remove(args.userContextId); + + this._refreshNeeded(); + return Promise.resolve(removed); }, // Styling the window + configureWindows() { + for (let window of windows.browserWindows) { // eslint-disable-line prefer-const + this.configureWindow(viewFor(window)); + } + }, + configureWindow(window) { const id = windowUtils.getInnerId(window); if (!(id in this._windowMap)) { @@ -492,6 +504,11 @@ const ContainerService = { delete this._windowMap[id]; }, + _refreshNeeded() { + // FIXME: color/name propagation + this.configureWindows(); + }, + _hideAllPanels() { for (let id in this._windowMap) { // eslint-disable-line prefer-const this._windowMap[id].hidePanel(); From a359746972ab393118f322b5c7a38ebe0b5ed2b5 Mon Sep 17 00:00:00 2001 From: baku Date: Sun, 22 Jan 2017 17:35:04 +0100 Subject: [PATCH 3/5] Better icon/color mapping between addon and m-b --- index.js | 116 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index af9a3dd..bc4f941 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,25 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const HIDE_MENU_TIMEOUT = 1000; -const IDENTITY_COLORS = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple"]; + +const IDENTITY_COLORS = [ + { name: "blue", color: "#00a7e0" }, + { name: "turquoise", color: "#01bdad" }, + { name: "green", color: "#7dc14c" }, + { name: "yellow", color: "#ffcb00" }, + { name: "orange", color: "#f89c24" }, + { name: "red", color: "#d92215" }, + { name: "pink", color: "#ee5195" }, + { name: "purple", color: "#7a2f7a" }, +]; + +const IDENTITY_ICONS = [ + { name: "fingerprint", image: "chrome://browser/skin/usercontext/personal.svg" }, + { name: "briefcase", image: "chrome://browser/skin/usercontext/work.svg" }, + { name: "dollar", image: "chrome://browser/skin/usercontext/banking.svg" }, + { name: "cart", image: "chrome://browser/skin/usercontext/shopping.svg" }, + { name: "cirlce", image: "" }, // this doesn't exist in m-b +]; const { attachTo } = require("sdk/content/mod"); const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); @@ -124,51 +142,61 @@ const ContainerService = { // utility methods _convert(identity) { - // In FF 50-51, the icon is the full path, in 52 and following - // releases, we have IDs to be used with a svg file. In this function - // we map URLs to svg IDs. - let image, color; - - if (identity.icon === "fingerprint" || - identity.icon === "chrome://browser/skin/usercontext/personal.svg") { - image = "fingerprint"; - } else if (identity.icon === "briefcase" || - identity.icon === "chrome://browser/skin/usercontext/work.svg") { - image = "briefcase"; - } else if (identity.icon === "dollar" || - identity.icon === "chrome://browser/skin/usercontext/banking.svg") { - image = "dollar"; - } else if (identity.icon === "cart" || - identity.icon === "chrome://browser/skin/usercontext/shopping.svg") { - image = "cart"; - } else { - image = "circle"; - } - - if (identity.color === "#00a7e0") { - color = "blue"; - } else if (identity.color === "#f89c24") { - color = "orange"; - } else if (identity.color === "#7dc14c") { - color = "green"; - } else if (identity.color === "#ee5195") { - color = "pink"; - } else if (IDENTITY_COLORS.indexOf(identity.color) !== -1) { - color = identity.color; - } else { - color = ""; - } - + // Let's convert the known colors to their color names. return { name: ContextualIdentityService.getUserContextLabel(identity.userContextId), - image, - color, + image: this._fromIconToName(identity.icon), + color: this._fromColorToName(identity.color), userContextId: identity.userContextId, hasHiddenTabs: !!this._identitiesState[identity.userContextId].hiddenTabUrls.length, hasOpenTabs: !!this._identitiesState[identity.userContextId].openTabs }; }, + // In FF 50-51, the icon is the full path, in 52 and following + // releases, we have IDs to be used with a svg file. In this function + // we map URLs to svg IDs. + + // Helper methods for converting colors to names and names to colors. + + _fromNameToColor(name) { + return this._fromNameOrColor(name, "color"); + }, + + _fromColorToName(color) { + return this._fromNameOrColor(color, "name"); + }, + + _fromNameOrColor(what, attribute) { + for (let color of IDENTITY_COLORS) { // eslint-disable-line prefer-const + if (what === color.color || what === color.name) { + return color[attribute]; + } + } + return ""; + }, + + // Helper methods for converting icons to names and names to icons. + + _fromNameToIcon(name) { + return this._fromNameOrIcon(name, "image", ""); + }, + + _fromIconToName(icon) { + return this._fromNameOrIcon(icon, "name", "circle"); + }, + + _fromNameOrIcon(what, attribute, defaultValue) { + for (let icon of IDENTITY_ICONS) { // eslint-disable-line prefer-const + if (what === icon.image || what === icon.name) { + return icon[attribute]; + } + } + return defaultValue; + }, + + // Tab Helpers + _getUserContextIdFromTab(tab) { return parseInt(viewFor(tab).getAttribute("usercontextid") || 0, 10); }, @@ -431,8 +459,10 @@ const ContainerService = { } } - // FIXME: icon and color conversion based on FF version. - const identity = ContextualIdentityService.create(args.name, args.icon, args.color); + const color = this._fromNameToColor(args.color); + const icon = this._fromNameToIcon(args.icon); + + const identity = ContextualIdentityService.create(args.name, icon, color); this._identitiesState[identity.userContextId] = { hiddenTabUrls: [], @@ -456,12 +486,12 @@ const ContainerService = { } } - // FIXME: icon and color conversion based on FF version. + const color = this._fromNameToColor(identity.color); + const icon = this._fromNameToIcon(identity.icon); const updated = ContextualIdentityService.update(args.userContextId, identity.name, - identity.icon, - identity.color); + icon, color); this._refreshNeeded(); return Promise.resolve(updated); From 72ec2a5731ddda3be5259e3791206c5aa6e14a1e Mon Sep 17 00:00:00 2001 From: baku Date: Sun, 22 Jan 2017 17:35:52 +0100 Subject: [PATCH 4/5] Debug messages removed --- webextension/js/popup.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 1dca2a4..ad952a6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -5,9 +5,6 @@ const CONTAINER_HIDE_SRC = "/img/container-hide.svg"; const CONTAINER_UNHIDE_SRC = "/img/container-unhide.svg"; -// TODO: Let's set it to false before releasing!!! -const DEBUG = true; - // List of panels const P_ONBOARDING_1 = "onboarding1"; const P_ONBOARDING_2 = "onboarding2"; @@ -17,12 +14,6 @@ const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_DELETE = "containerDelete"; -function log(...args) { - if (DEBUG) { - console.log.call(console, ...args); // eslint-disable-line no-console - } -} - // This object controls all the panels, identities and many other things. const Logic = { _identities: [], @@ -204,7 +195,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const fragment = document.createDocumentFragment(); Logic.identities().forEach(identity => { - log("identities.forEach"); const tr = document.createElement("tr"); fragment.appendChild(tr); tr.classList.add("container-panel-row", "clickable"); @@ -314,7 +304,6 @@ Logic.registerPanel(P_CONTAINER_INFO, { method: "getTabs", userContextId: identity.userContextId, }).then(tabs => { - log("browser.runtime.sendMessage getTabs, tabs: ", tabs); // For each one, let's create a new line. const fragment = document.createDocumentFragment(); for (let tab of tabs) { // eslint-disable-line prefer-const From bed0a23c788ee42b36e0fffccac49290a3d0d906 Mon Sep 17 00:00:00 2001 From: baku Date: Sun, 22 Jan 2017 18:44:26 +0100 Subject: [PATCH 5/5] Refresh of container icon+name into the awesome bar --- index.js | 117 +++++++++++++++++++++++++++------------ webextension/js/popup.js | 16 +++++- 2 files changed, 96 insertions(+), 37 deletions(-) diff --git a/index.js b/index.js index bc4f941..01a4afc 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ const IDENTITY_ICONS = [ const { attachTo } = require("sdk/content/mod"); const { ContextualIdentityService } = require("resource://gre/modules/ContextualIdentityService.jsm"); const { getFavicon } = require("sdk/places/favicon"); +const { modelFor } = require("sdk/model/core"); const self = require("sdk/self"); const { Style } = require("sdk/stylesheet/style"); const tabs = require("sdk/tabs"); @@ -110,16 +111,17 @@ const ContainerService = { this._hideAllPanels(); }); - tabs.on("activate", () => { + tabs.on("activate", tab => { this._hideAllPanels(); + this._restyleTab(tab).catch(() => {}); }); // Modify CSS and other stuff for each window. - this.configureWindows(); + this.configureWindows().catch(() => {}); windows.browserWindows.on("open", window => { - this.configureWindow(viewFor(window)); + this.configureWindow(viewFor(window)).catch(() => {}); }); windows.browserWindows.on("close", window => { @@ -469,9 +471,11 @@ const ContainerService = { openTabs: 0 }; - this._refreshNeeded(); - - return Promise.resolve(this._convert(identity)); + this._refreshNeeded().then(() => { + return this._convert(identity); + }).catch(() => { + return this._convert(identity); + }); }, updateIdentity(args) { @@ -493,8 +497,11 @@ const ContainerService = { identity.name, icon, color); - this._refreshNeeded(); - return Promise.resolve(updated); + this._refreshNeeded().then(() => { + return updated; + }).catch(() => { + return updated; + }); }, removeIdentity(args) { @@ -508,16 +515,21 @@ const ContainerService = { const removed = ContextualIdentityService.remove(args.userContextId); - this._refreshNeeded(); - return Promise.resolve(removed); + this._refreshNeeded().then(() => { + return removed; + }).catch(() => { + return removed; + }); }, // Styling the window configureWindows() { + const promises = []; for (let window of windows.browserWindows) { // eslint-disable-line prefer-const - this.configureWindow(viewFor(window)); + promises.push(this.configureWindow(viewFor(window))); } + return Promise.all(promises); }, configureWindow(window) { @@ -526,7 +538,7 @@ const ContainerService = { this._windowMap[id] = new ContainerWindow(window); } - this._windowMap[id].configure(); + return this._windowMap[id].configure(); }, closeWindow(window) { @@ -535,8 +547,7 @@ const ContainerService = { }, _refreshNeeded() { - // FIXME: color/name propagation - this.configureWindows(); + return this.configureWindows(); }, _hideAllPanels() { @@ -544,6 +555,30 @@ const ContainerService = { this._windowMap[id].hidePanel(); } }, + + _restyleTab(tab) { + if (!tab) { + return Promise.resolve(null); + } + + const userContextId = ContainerService._getUserContextIdFromTab(tab); + return ContainerService.getIdentity({userContextId}).then(identity => { + if (!identity) { + return; + } + + const hbox = viewFor(tab.window).document.getElementById("userContext-icons"); + hbox.setAttribute("data-identity-color", identity.color); + + const label = viewFor(tab.window).document.getElementById("userContext-label"); + label.setAttribute("value", identity.name); + label.style.color = ContainerService._fromNameToColor(identity.color); + + const indicator = viewFor(tab.window).document.getElementById("userContext-indicator"); + indicator.setAttribute("data-identity-icon", identity.image); + indicator.style.listStyleImage = ""; + }); + }, }; // ---------------------------------------------------------------------------- @@ -566,6 +601,13 @@ ContainerWindow.prototype = { }, configure() { + return Promise.all([ + this._configurePlusButtonMenu(), + this._configureActiveTab(), + ]); + }, + + _configurePlusButtonMenu() { const tabsElement = this._window.document.getElementById("tabbrowser-tabs"); const button = this._window.document.getAnonymousElementByAttribute(tabsElement, "anonid", "tabs-newtab-button"); @@ -585,7 +627,28 @@ ContainerWindow.prototype = { this._repositionPopup(); - ContainerService.queryIdentities().then(identities => { + button.addEventListener("click", () => { + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseover", () => { + this._repositionPopup(); + this._panelElement.hidden = false; + }); + + button.addEventListener("mouseout", () => { + this._createTimeout(); + }); + + this._panelElement.addEventListener("mouseout", (e) => { + if (e.target !== this._panelElement) { + this._createTimeout(); + return; + } + this._repositionPopup(); + }); + + return ContainerService.queryIdentities().then(identities => { identities.forEach(identity => { const menuItemElement = this._window.document.createElementNS(XUL_NS, "menuitem"); this._panelElement.appendChild(menuItemElement); @@ -619,27 +682,11 @@ ContainerWindow.prototype = { }).catch(() => { this.hidePanel(); }); + }, - button.addEventListener("click", () => { - this._panelElement.hidden = false; - }); - - button.addEventListener("mouseover", () => { - this._repositionPopup(); - this._panelElement.hidden = false; - }); - - button.addEventListener("mouseout", () => { - this._createTimeout(); - }); - - this._panelElement.addEventListener("mouseout", (e) => { - if (e.target !== this._panelElement) { - this._createTimeout(); - return; - } - this._repositionPopup(); - }); + _configureActiveTab() { + const tab = modelFor(this._window).tabs.activeTab; + return ContainerService._restyleTab(tab); }, // This function puts the popup in the correct place. diff --git a/webextension/js/popup.js b/webextension/js/popup.js index ad952a6..1d588b5 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -420,8 +420,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, { method: identity.userContextId ? "updateIdentity" : "createIdentity", userContextId: identity.userContextId || 0, name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), - icon: identity.image || "fingerprint", - color: identity.color || "green", + icon: this._randomIcon(), + color: this._randomColor(), }).then(() => { return Logic.refreshIdentities(); }).then(() => { @@ -441,6 +441,18 @@ Logic.registerPanel(P_CONTAINER_EDIT, { return Promise.resolve(null); }, + + // These 2 methods are for debugging only. We should remove them when we + // finish the UI. + _randomIcon() { + const images = ["fingerprint", "briefcase", "dollar", "cart", "cirlce"]; + return images[Math.floor(Math.random() * images.length)]; + }, + + _randomColor() { + const colors = ["blue", "turquoise", "green", "yellow", "orange", "red", "pink", "purple" ]; + return colors[Math.floor(Math.random() * colors.length)]; + }, }); // P_CONTAINER_DELETE: Delete a container.