diff --git a/package.json b/package.json index 90dce9a..32c6270 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "bugs": { "url": "https://github.com/mozilla/multi-account-containers/issues" }, - "dependencies": {}, "devDependencies": { "addons-linter": "^1.3.2", "ajv": "^6.6.2", diff --git a/src/css/popup.css b/src/css/popup.css index eeeaf74..8957dfd 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -593,7 +593,6 @@ span ~ .panel-header-text { } .userContext-wrapper { - align-items: center; display: flex; flex: 1 1; transition: background-color 75ms; @@ -610,6 +609,23 @@ span ~ .panel-header-text { margin-inline-start: var(--inline-icon-space-size); } +.userContext-name-wrapper { + align-self: center; + flex: 1; +} + +.container-panel-row .container-lockorunlock { + align-items: center; + display: flex; + flex: 0 0 var(--icon-button-size); +} + +.container-panel-row .container-lockorunlock img { + align-self: center; + block-size: 20px; + flex: 1; +} + /* .userContext-icon is used natively, Bug 1333811 was raised to fix */ .usercontext-icon { background-image: var(--identity-icon); @@ -926,12 +942,24 @@ span ~ .panel-header-text { } /* https://github.com/mozilla/multi-account-containers/issues/847 */ -.container-lockorunlock.container-locked * { - filter: invert(0.5) sepia(1) saturate(127) hue-rotate(360deg); +.container-lockorunlock * { + fill: #979797; + filter: url('/img/filters.svg#fill'); } -.container-lockorunlock.container-unlocked * { - filter: invert(0.5); +.container-lockorunlock:hover * { + fill: black; + filter: url('/img/filters.svg#fill'); +} + +.container-lockorunlock.container-locked * { + fill: red; + filter: url('/img/filters.svg#fill'); +} + +.container-lockorunlock.container-locked:hover * { + fill: #ba1111; + filter: url('/img/filters.svg#fill'); } /* Achievement panel elements */ diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 575eab2..f937c43 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -92,6 +92,16 @@ const assignManager = { } }); return sites; + }, + + async getNumbersOfAssignments() { + return Object.values(await this.area.get()).reduce((result, site) => { + const userContextId = site.userContextId; + if (userContextId !== undefined) { + result[userContextId] = (result[userContextId] || 0) + 1; + } + return result; + }, {}); } }, @@ -141,9 +151,9 @@ const assignManager = { return {}; } const userContextId = this.getUserContextIdFromCookieStore(tab); - + // https://github.com/mozilla/multi-account-containers/issues/847 - // + // // Handle the case where this request's URL is not assigned to any particular // container. We must do the following check: // @@ -160,7 +170,7 @@ const assignManager = { // - the incoming request is for "www.amazon.com", which has no specific container assignment // - in this case, we must re-open "www.amazon.com" in a new tab in the default container const mustReloadPageInDefaultContainerDueToLocking = await this._determineMustReloadPageInDefaultContainerDueToLocking(siteSettings, tab); - + if (!mustReloadPageInDefaultContainerDueToLocking) { if (!siteSettings || userContextId === siteSettings.userContextId @@ -248,20 +258,20 @@ const assignManager = { cancel: true, }; }, - + async _determineMustReloadPageInDefaultContainerDueToLocking(siteSettings, tab) { // Tab doesn't support cookies, so containers not supported either. if (!("cookieStoreId" in tab)) { return false; } - + // Requested page has been assigned to a specific container. // I.e. it will be opened in that container anyway, so we don't need to check if the // current tab's container is locked or not. if (siteSettings) { return false; } - + // Requested page is not assigned to a specific container. If the current tab's container // is locked, then the page must be reloaded in the default container. const currentContainerState = await identityState.storageArea.get(tab.cookieStoreId); @@ -445,14 +455,14 @@ const assignManager = { neverAsk: false }, exemptedTabIds); actionName = "added"; - + } else { // Remove assignment await this.storageArea.remove(pageUrl); actionName = "removed"; - + // Unlock container if now empty - await this._unlockContainerIfHasNoAssignments(userContextId); + await this._unlockContainerIfHasNoAssignments(userContextId); } browser.tabs.sendMessage(tabId, { text: `Successfully ${actionName} site to always open in this container` @@ -460,7 +470,7 @@ const assignManager = { const tab = await browser.tabs.get(tabId); this.calculateContextMenu(tab); }, - + async _unlockContainerIfHasNoAssignments(userContextId) { const assignments = await this.storageArea.getByContainer(userContextId); const hasAssignments = assignments && Object.keys(assignments).length > 0; @@ -548,21 +558,21 @@ const assignManager = { return `%${charCode}`; }); }, - + reloadPageInDefaultContainer(url, index, active, openerTabId) { // To create a new tab in the default container, it is easiest just to omit the // cookieStoreId entirely. - // + // // Unfortunately, if you create a new tab WITHOUT a cookieStoreId but WITH an openerTabId, // then the new tab automatically inherits the opener tab's cookieStoreId. // I.e. it opens in the wrong container! - // + // // So we have to explicitly pass in a cookieStoreId when creating the tab, since we // are specifying the openerTabId. There doesn't seem to be any way // to look up the default container's cookieStoreId programatically, so sadly // we have to hardcode it here as "firefox-default". This is potentially // not cross-browser compatible. - // + // // Note that we could have just omitted BOTH cookieStoreId and openerTabId. But the // drawback then is that if the user later closes the newly-created tab, the browser // does not automatically return to the original opener tab. To get this desired behaviour, diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 910a42e..23f3ec7 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -128,7 +128,7 @@ const backgroundLogic = { if (!("userContextId" in options)) { return Promise.reject("lockOrUnlockContainer must be called with userContextId argument."); } - + const cookieStoreId = this.cookieStoreId(options.userContextId); const containerState = await identityState.storageArea.get(cookieStoreId); if (options.isLocked) { @@ -138,7 +138,7 @@ const backgroundLogic = { } return await identityState.storageArea.set(cookieStoreId, containerState); }, - + async moveTabsToWindow(options) { const requiredArguments = ["cookieStoreId", "windowId"]; @@ -251,6 +251,15 @@ const backgroundLogic = { return; }); await Promise.all(identitiesPromise); + + // Add number of assignments for each identity + const numbersOfAssignments = await assignManager.storageArea.getNumbersOfAssignments(); + Object.keys(numbersOfAssignments).forEach(userContextId => { + const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); + const identity = identitiesOutput[cookieStoreId]; + if (identity) { identity.numberOfAssignments = numbersOfAssignments[userContextId]; } + }); + return identitiesOutput; }, diff --git a/src/js/popup.js b/src/js/popup.js index 4e90df9..81630aa 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -254,6 +254,7 @@ const Logic = { identity.hasHiddenTabs = stateObject.hasHiddenTabs; identity.numberOfHiddenTabs = stateObject.numberOfHiddenTabs; identity.numberOfOpenTabs = stateObject.numberOfOpenTabs; + identity.numberOfAssignments = stateObject.numberOfAssignments; identity.isLocked = stateObject.isLocked; } return identity; @@ -370,7 +371,7 @@ const Logic = { value }); }, - + lockOrUnlockContainer(userContextId, isLocked) { return browser.runtime.sendMessage({ method: "lockOrUnlockContainer", @@ -381,6 +382,29 @@ const Logic = { }); }, + showLockState(element, identity) { + const icon = element.querySelector(".icon"); + const label = element.querySelector(".label"); + const titled = label || element; + + if (icon) { icon.src = identity.isLocked ? CONTAINER_LOCKED_SRC : CONTAINER_UNLOCKED_SRC; } + if (label) { label.innerText = identity.isLocked ? "Locked" : "Unlocked"; } + titled.setAttribute("title", `${identity.isLocked ? "Lock" : "Unlock"} ${identity.name} container`); + + element.classList.remove("container-locked", "container-unlocked"); + element.classList.add("container-lockorunlock", identity.isLocked ? "container-locked" : "container-unlocked"); + }, + + async toggleLock(element, identity) { + try { + await Logic.lockOrUnlockContainer(Logic.userContextId(identity.cookieStoreId), !identity.isLocked); + identity.isLocked = !identity.isLocked; + this.showLockState(element, identity); + } catch (e) { + throw new Error("Failed to lock/unlock container: " + e.message); + } + }, + generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -678,9 +702,11 @@ Logic.registerPanel(P_CONTAINERS_LIST, { Logic.identities().forEach(identity => { const hasTabs = (identity.hasHiddenTabs || identity.hasOpenTabs); + const hasAssignments = identity.numberOfAssignments > 0; const tr = document.createElement("tr"); const context = document.createElement("td"); const manage = document.createElement("td"); + const lock = document.createElement("div"); tr.classList.add("container-panel-row"); @@ -696,9 +722,13 @@ Logic.registerPanel(P_CONTAINERS_LIST, { data-identity-color="${identity.color}"> -
`; +