diff --git a/package.json b/package.json index 7b52d8b..b57cc35 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,14 @@ "name": "testpilot-containers", "title": "Multi-Account Containers", "description": "Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.", - "version": "6.0.0", + "version": "6.0.1", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "bugs": { "url": "https://github.com/mozilla/multi-account-containers/issues" }, "dependencies": {}, "devDependencies": { + "ajv": "^6.6.2", "addons-linter": "^1.3.2", "chai": "^4.1.2", "eslint": "^3.17.1", diff --git a/src/css/popup.css b/src/css/popup.css index 6eb6496..f2423a3 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -45,6 +45,7 @@ body { --small-text-size: 0.833rem; /* 10px */ --small-radius: 3px; --icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */ + --column-panel-inline-size: 268px; --inactive-opacity: 0.3; } @@ -267,7 +268,7 @@ table { .column-panel-content { display: flex; flex-direction: column; - inline-size: 268px; + inline-size: var(--column-panel-inline-size); } .column-panel-content .panel-footer { @@ -538,7 +539,7 @@ span ~ .panel-header-text { } #current-tab > label > input:checked { - background-image: url("chrome://global/skin/in-content/check.svg#check-native"); + background-image: url("/img/check.svg"); background-position: -1px -1px; background-size: var(--icon-size); } @@ -659,7 +660,11 @@ span ~ .panel-header-text { /* Container info list */ .container-info-tab-title { - flex: 1; + display: flex; +} + +.container-info-tab-row:hover .container-info-tab-title .truncate-text { + inline-size: calc(var(--column-panel-inline-size) - 58px); } #container-info-hideorshow { @@ -676,6 +681,21 @@ span ~ .panel-header-text { opacity: 0.3; } +.container-close-tab { + transform: scale(0.7); + visibility: collapse; +} + +.container-info-tab-row:hover .container-close-tab { + opacity: 0.5; + visibility: visible; +} + +.container-info-tab-row .container-close-tab:hover { + opacity: 1; + visibility: visible; +} + .container-info-has-tabs, .container-info-tab-row { align-items: center; @@ -702,10 +722,6 @@ span ~ .panel-header-text { margin-inline-end: 0; } -.container-info-tab-row td { - max-inline-size: 200px; -} - .container-info-list { display: flex; flex-direction: column; diff --git a/src/img/blank-favicon.svg b/src/img/blank-favicon.svg new file mode 100644 index 0000000..c2393b6 --- /dev/null +++ b/src/img/blank-favicon.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/img/check.svg b/src/img/check.svg new file mode 100644 index 0000000..bcbcfc0 --- /dev/null +++ b/src/img/check.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/img/container-close-tab.svg b/src/img/container-close-tab.svg new file mode 100644 index 0000000..a36fa01 --- /dev/null +++ b/src/img/container-close-tab.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 04906d8..89f9159 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -6,6 +6,7 @@ const backgroundLogic = { "about:home", "about:blank" ]), + unhideQueue: [], async getExtensionInfo() { const manifestPath = browser.extension.getURL("manifest.json"); @@ -112,6 +113,17 @@ const backgroundLogic = { return list.concat(containerState.hiddenTabs); }, + async unhideContainer(cookieStoreId) { + if (!this.unhideQueue.includes(cookieStoreId)) { + this.unhideQueue.push(cookieStoreId); + await this.showTabs({ + cookieStoreId + }); + this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1); + } + }, + + async moveTabsToWindow(options) { const requiredArguments = ["cookieStoreId", "windowId"]; this.checkArgs(requiredArguments, options, "moveTabsToWindow"); @@ -123,6 +135,7 @@ const backgroundLogic = { }); const containerState = await identityState.storageArea.get(cookieStoreId); + // Nothing to do if (list.length === 0 && containerState.hiddenTabs.length === 0) { @@ -152,12 +165,15 @@ const backgroundLogic = { const showHiddenPromises = []; // Let's show the hidden tabs. - for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const - showHiddenPromises.push(browser.tabs.create({ - url: object.url || DEFAULT_TAB, - windowId: newWindowObj.id, - cookieStoreId - })); + if (!this.unhideQueue.includes(cookieStoreId)) { + this.unhideQueue.push(cookieStoreId); + for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const + showHiddenPromises.push(browser.tabs.create({ + url: object.url || DEFAULT_TAB, + windowId: newWindowObj.id, + cookieStoreId + })); + } } if (hiddenDefaultTabToClose) { @@ -176,7 +192,9 @@ const backgroundLogic = { browser.tabs.remove(tab.id); } } - return await identityState.storageArea.set(cookieStoreId, containerState); + const rv = await identityState.storageArea.set(cookieStoreId, containerState); + this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1); + return rv; }, async _closeTabs(userContextId, windowId = false) { @@ -209,7 +227,9 @@ const backgroundLogic = { }); identitiesOutput[cookieStoreId] = { hasHiddenTabs: !!containerState.hiddenTabs.length, - hasOpenTabs: !!openTabs.length + hasOpenTabs: !!openTabs.length, + numberOfHiddenTabs: containerState.hiddenTabs.length, + numberOfOpenTabs: openTabs.length }; return; }); @@ -307,4 +327,3 @@ const backgroundLogic = { return `firefox-container-${userContextId}`; } }; - diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index 6e5fced..9fbe88e 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -3,7 +3,6 @@ const messageHandler = { // We use this to catch redirected tabs that have just opened // If this were in platform we would change how the tab opens based on "new tab" link navigations such as ctrl+click LAST_CREATED_TAB_TIMER: 2000, - unhideQueue: [], init() { // Handles messages from webextension code @@ -39,7 +38,7 @@ const messageHandler = { backgroundLogic.sortTabs(); break; case "showTabs": - this.unhideContainer(m.cookieStoreId); + backgroundLogic.unhideContainer(m.cookieStoreId); break; case "hideTabs": backgroundLogic.hideTabs({ @@ -156,7 +155,7 @@ const messageHandler = { this.incrementCountOfContainerTabsOpened(); } - this.unhideContainer(tab.cookieStoreId); + backgroundLogic.unhideContainer(tab.cookieStoreId); } setTimeout(() => { this.lastCreatedTab = null; @@ -182,17 +181,6 @@ const messageHandler = { } }, - async unhideContainer(cookieStoreId) { - if (!this.unhideQueue.includes(cookieStoreId)) { - this.unhideQueue.push(cookieStoreId); - // Unhide all hidden tabs - await backgroundLogic.showTabs({ - cookieStoreId - }); - this.unhideQueue.splice(this.unhideQueue.indexOf(cookieStoreId), 1); - } - }, - async onFocusChangedCallback(windowId) { assignManager.removeContextMenu(); // browserAction loses background color in new windows ... diff --git a/src/js/popup.js b/src/js/popup.js index 870b9a4..7593bea 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -162,7 +162,7 @@ const Logic = { async clearBrowserActionBadge() { const extensionInfo = await getExtensionInfo(); const storage = await browser.storage.local.get({browserActionBadgesClicked: []}); - browser.browserAction.setBadgeBackgroundColor({color: ""}); + browser.browserAction.setBadgeBackgroundColor({color: null}); browser.browserAction.setBadgeText({text: ""}); storage.browserActionBadgesClicked.push(extensionInfo.version); // use set and spread to create a unique array @@ -177,7 +177,9 @@ const Logic = { name: "Default", cookieStoreId, icon: "default-tab", - color: "default-tab" + color: "default-tab", + numberOfHiddenTabs: 0, + numberOfOpenTabs: 0 }; // Handle old style rejection with null and also Promise.reject new style try { @@ -248,6 +250,8 @@ const Logic = { if (stateObject) { identity.hasOpenTabs = stateObject.hasOpenTabs; identity.hasHiddenTabs = stateObject.hasHiddenTabs; + identity.numberOfHiddenTabs = stateObject.numberOfHiddenTabs; + identity.numberOfOpenTabs = stateObject.numberOfOpenTabs; } return identity; }); @@ -801,20 +805,44 @@ Logic.registerPanel(P_CONTAINER_INFO, { tr.classList.add("container-info-tab-row"); tr.innerHTML = escaped` - ${tab.title}`; +
${tab.title}
`; tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favIconUrl)); - + document.getElementById("container-info-table").appendChild(fragment); + // On click, we activate this tab. But only if this tab is active. if (!tab.hiddenState) { + const closeImage = document.createElement("img"); + closeImage.src = "/img/container-close-tab.svg"; + closeImage.className = "container-close-tab"; + closeImage.title = "Close tab"; + closeImage.id = tab.id; + const tabTitle = tr.querySelector(".container-info-tab-title"); + tabTitle.appendChild(closeImage); + + // On hover, we add truncate-text class to add close-tab-image after tab title truncates + const tabTitleHoverEvent = () => { + tabTitle.classList.toggle("truncate-text"); + tr.querySelector(".container-tab-title").classList.toggle("truncate-text"); + }; + + tr.addEventListener("mouseover", tabTitleHoverEvent); + tr.addEventListener("mouseout", tabTitleHoverEvent); + tr.classList.add("clickable"); Logic.addEnterHandler(tr, async function () { await browser.tabs.update(tab.id, {active: true}); window.close(); }); - } - } - document.getElementById("container-info-table").appendChild(fragment); + const closeTab = document.getElementById(tab.id); + if (closeTab) { + Logic.addEnterHandler(closeTab, async function(e) { + await browser.tabs.remove(Number(e.target.id)); + window.close(); + }); + } + } + } }, }); @@ -1074,9 +1102,17 @@ Logic.registerPanel(P_CONTAINER_DELETE, { prepare() { const identity = Logic.currentIdentity(); - // Populating the panel: name and icon + // Populating the panel: name, icon, and warning message document.getElementById("delete-container-name").textContent = identity.name; + const totalNumberOfTabs = identity.numberOfHiddenTabs + identity.numberOfOpenTabs; + let warningMessage = ""; + if (totalNumberOfTabs > 0) { + const grammaticalNumTabs = totalNumberOfTabs > 1 ? "tabs" : "tab"; + warningMessage = `If you remove this container now, ${totalNumberOfTabs} container ${grammaticalNumTabs} will be closed.`; + } + document.getElementById("delete-container-tab-warning").textContent = warningMessage; + const icon = document.getElementById("delete-container-icon"); icon.setAttribute("data-identity-icon", identity.icon); icon.setAttribute("data-identity-color", identity.color); diff --git a/src/js/utils.js b/src/js/utils.js index 5d5046b..804e9d9 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -1,4 +1,4 @@ -const DEFAULT_FAVICON = "moz-icon://goat?size=16"; +const DEFAULT_FAVICON = "/img/blank-favicon.svg"; // TODO use export here instead of globals window.Utils = { diff --git a/src/manifest.json b/src/manifest.json index f3fa049..d624d32 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Firefox Multi-Account Containers", - "version": "6.0.0", + "version": "6.0.1", "description": "Multi-Account Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.", "icons": { diff --git a/src/popup.html b/src/popup.html index a28dd32..4cd8219 100644 --- a/src/popup.html +++ b/src/popup.html @@ -204,7 +204,7 @@

Remove This Container

-

If you remove this container now, container tabs will be closed. Are you sure you want to remove this Container?

+

Are you sure you want to remove this Container?