From 3668e17cac1b65fc876f4d04e4ce12c7d1d0a23b Mon Sep 17 00:00:00 2001 From: Francis McKenzie Date: Sun, 14 Feb 2021 21:45:50 +0100 Subject: [PATCH] container-locking - add lock icons to container list panel container-locking - fix linting errors from last commit container-locking - Fix linting errors with previous commit container-locking - change assignManager function to getNumbersOfAssignments --- package.json | 1 - src/css/popup.css | 38 ++++++++++-- src/js/background/assignManager.js | 38 +++++++----- src/js/background/backgroundLogic.js | 13 ++++- src/js/popup.js | 87 ++++++++++++++++++---------- 5 files changed, 126 insertions(+), 51 deletions(-) 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}"> -
`; +
+
+
`; context.querySelector(".container-name").textContent = identity.name; manage.innerHTML = ""; + lock.innerHTML = ""; + Logic.showLockState(lock, identity); fragment.appendChild(tr); @@ -708,9 +738,15 @@ Logic.registerPanel(P_CONTAINERS_LIST, { tr.appendChild(manage); } + if (hasAssignments) { + context.appendChild(lock); + } + Logic.addEnterHandler(tr, async (e) => { - if (e.target.matches(".open-newtab") - || e.target.parentNode.matches(".open-newtab") + if (e.target.closest(".container-lockorunlock")) { + const lock = e.target.closest(".container-lockorunlock"); + Logic.toggleLock(lock, identity); + } else if (e.target.closest(".open-newtab") || e.type === "keydown") { try { browser.tabs.create({ @@ -929,7 +965,9 @@ Logic.registerPanel(P_CONTAINERS_EDIT, { data-identity-color="${identity.color}"> -
+
+
+
0); @@ -1035,30 +1073,21 @@ Logic.registerPanel(P_CONTAINER_EDIT, { while (tableElement.firstChild) { tableElement.firstChild.remove(); } - + /* Container locking: https://github.com/mozilla/multi-account-containers/issues/847 */ - const lockOrUnlockIcon = isLocked ? CONTAINER_LOCKED_SRC : CONTAINER_UNLOCKED_SRC; - const lockOrUnlockLabel = isLocked ? "Locked" : "Unlocked"; - const lockOrUnlockClass = isLocked ? "container-locked" : "container-unlocked"; - const lockElement = document.createElement("div"); - lockElement.innerHTML = escaped` - -
- ${lockOrUnlockLabel} -
`; - lockElement.classList.add("container-info-tab-row", "clickable", "container-lockorunlock", lockOrUnlockClass); - tableElement.appendChild(lockElement); - - Logic.addEnterHandler(lockElement, async () => { - try { - await Logic.lockOrUnlockContainer(Logic.currentUserContextId(), !isLocked); - this.showAssignedContainers(assignments, !isLocked); - } catch (e) { - throw new Error("Failed to lock/unlock container: " + e.message); - } + const lock = document.createElement("div"); + lock.innerHTML = escaped` + +
`; + lock.classList.add("container-info-tab-row", "clickable"); + Logic.showLockState(lock, identity); + tableElement.appendChild(lock); + + Logic.addEnterHandler(lock, () => { + Logic.toggleLock(lock, identity); }); /* Container locking end */ - + /* Assignment list */ assignmentKeys.forEach((siteKey) => { const site = assignments[siteKey]; @@ -1085,7 +1114,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const currentTab = await Logic.currentTab(); Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true); delete assignments[siteKey]; - that.showAssignedContainers(assignments, isLocked); + that.showAssignedContainers(assignments, identity); }); trElement.classList.add("container-info-tab-row", "clickable"); tableElement.appendChild(trElement); @@ -1129,7 +1158,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const userContextId = Logic.currentUserContextId(); const assignments = await Logic.getAssignmentObjectByContainer(userContextId); - this.showAssignedContainers(assignments, identity.isLocked); + this.showAssignedContainers(assignments, identity); document.querySelector("#edit-container-panel .panel-footer").hidden = !!userContextId; document.querySelector("#edit-container-panel-name-input").value = identity.name || "";