diff --git a/.eslintrc.js b/.eslintrc.js index f2a7957..d9f7270 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { "webextensions": true }, "globals": { + "Utils": true, "CustomizableUI": true, "CustomizableWidgets": true, "SessionStore": true, diff --git a/webextension/background.js b/webextension/background.js index ca01d89..ebedf66 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -56,19 +56,25 @@ const assignManager = { return this.area.remove([siteStoreKey]); }, - deleteContainer(userContextId) { - const removeKeys = []; - this.area.get().then((siteConfigs) => { - Object.keys(siteConfigs).forEach((key) => { - // For some reason this is stored as string... lets check them both as that - if (String(siteConfigs[key].userContextId) === String(userContextId)) { - removeKeys.push(key); - } - }); - this.area.remove(removeKeys); - }).catch((e) => { - throw e; + async deleteContainer(userContextId) { + const sitesByContainer = await this.getByContainer(userContextId); + this.area.remove(Object.keys(sitesByContainer)); + }, + + async getByContainer(userContextId) { + const sites = {}; + const siteConfigs = await this.area.get(); + Object.keys(siteConfigs).forEach((key) => { + // For some reason this is stored as string... lets check them both as that + if (String(siteConfigs[key].userContextId) === String(userContextId)) { + const site = siteConfigs[key]; + // In hindsight we should have stored this + // TODO file a follow up to clean the storage onLoad + site.hostname = key.replace(/^siteContainerMap@@_/, ""); + sites[key] = site; + } }); + return sites; } }, @@ -104,6 +110,7 @@ const assignManager = { if (options.frameId !== 0 || options.tabId === -1) { return {}; } + this.removeContextMenu(); return Promise.all([ browser.tabs.get(options.tabId), this.storageArea.get(options.url) @@ -151,15 +158,11 @@ const assignManager = { // let actionName; let remove; if (info.menuItemId === this.MENU_ASSIGN_ID) { - //actionName = "added"; - // storageAction = this._setAssignment(info.pageUrl, userContextId, setOrRemove); remove = false; } else { - // actionName = "removed"; - //storageAction = this.storageArea.remove(info.pageUrl); remove = true; } - await this._setOrRemoveAssignment(info.pageUrl, userContextId, remove); + await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove); this.calculateContextMenu(tab); } }, @@ -192,7 +195,7 @@ const assignManager = { return true; }, - async _setOrRemoveAssignment(pageUrl, userContextId, remove) { + async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) { let actionName; if (!remove) { await this.storageArea.set(pageUrl, { @@ -205,11 +208,8 @@ const assignManager = { await this.storageArea.remove(pageUrl); actionName = "removed"; } - browser.notifications.create({ - type: "basic", - title: "Containers", - message: `Successfully ${actionName} site to always open in this container`, - iconUrl: browser.extension.getURL("/img/onboarding-1.png") + browser.tabs.sendMessage(tabId, { + text: `Successfully ${actionName} site to always open in this container` }); backgroundLogic.sendTelemetryPayload({ event: `${actionName}-container-assignment`, @@ -227,7 +227,11 @@ const assignManager = { return false; }, - async calculateContextMenu(tab) { + _getByContainer(userContextId) { + return this.storageArea.getByContainer(userContextId); + }, + + removeContextMenu() { // There is a focus issue in this menu where if you change window with a context menu click // you get the wrong menu display because of async // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1215376#c16 @@ -235,6 +239,10 @@ const assignManager = { // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1352102 browser.contextMenus.remove(this.MENU_ASSIGN_ID); browser.contextMenus.remove(this.MENU_REMOVE_ID); + }, + + async calculateContextMenu(tab) { + this.removeContextMenu(); const siteSettings = await this._getAssignment(tab); // ✓ This is to mitigate https://bugzilla.mozilla.org/show_bug.cgi?id=1351418 let prefix = " "; // Alignment of non breaking space, unknown why this requires so many spaces to align with the tick @@ -429,10 +437,13 @@ const messageHandler = { return assignManager._getAssignment(tab); }); break; + case "getAssignmentObjectByContainer": + response = assignManager._getByContainer(m.message.userContextId); + break; case "setOrRemoveAssignment": response = browser.tabs.get(m.tabId).then((tab) => { const userContextId = assignManager.getUserContextIdFromCookieStore(tab); - return assignManager._setOrRemoveAssignment(tab.url, userContextId, m.value); + return assignManager._setOrRemoveAssignment(tab.id, tab.url, userContextId, m.value); }); break; case "exemptContainerAssignment": @@ -474,6 +485,7 @@ const messageHandler = { }); browser.tabs.onActivated.addListener((info) => { + assignManager.removeContextMenu(); browser.tabs.get(info.tabId).then((tab) => { tabPageCounter.initTabCounter(tab); assignManager.calculateContextMenu(tab); @@ -483,6 +495,7 @@ const messageHandler = { }); browser.windows.onFocusChanged.addListener((windowId) => { + assignManager.removeContextMenu(); browser.tabs.query({active: true, windowId}).then((tabs) => { if (tabs && tabs[0]) { tabPageCounter.initTabCounter(tabs[0]); @@ -511,6 +524,7 @@ const messageHandler = { if (details.frameId !== 0 || details.tabId === -1) { return {}; } + assignManager.removeContextMenu(); browser.tabs.get(details.tabId).then((tab) => { tabPageCounter.incrementTabCount(tab); diff --git a/webextension/confirm-page.html b/webextension/confirm-page.html index 09d4852..20d11c8 100644 --- a/webextension/confirm-page.html +++ b/webextension/confirm-page.html @@ -27,6 +27,7 @@ + diff --git a/webextension/css/content.css b/webextension/css/content.css new file mode 100644 index 0000000..885b0fb --- /dev/null +++ b/webextension/css/content.css @@ -0,0 +1,22 @@ +.container-notification { + background: #33f70c; + color: #003f07; + display: block; + inline-size: 100vw; + offset-block-start: 0; + offset-inline-start: 0; + padding-block-end: 8px; + padding-block-start: 8px; + padding-inline-end: 8px; + padding-inline-start: 8px; + position: fixed; + transform: translateY(-100%); + transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1) 0.3s; + z-index: 999999999999; +} + +.container-notification img { + block-size: 16px; + inline-size: 16px; + margin-inline-end: 3px; +} diff --git a/webextension/css/popup.css b/webextension/css/popup.css index ad2966e..a0f2c52 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -1,16 +1,55 @@ /* General Rules and Resets */ -body { - inline-size: 300px; - max-inline-size: 300px; +* { + font-size: inherit; + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: 0; + padding-inline-start: 0; } html { box-sizing: border-box; + font-size: 12px; +} + +body { + font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif; + inline-size: 300px; + max-inline-size: 300px; } :root { - --font-size-heading: 16px; --primary-action-color: #248aeb; + --title-text-color: #000; + --text-normal-color: #4a4a4a; + --text-heading-color: #000; + + /* calculated from 12px */ + --font-size-heading: 1.33rem; /* 16px */ + --block-line-space-size: 0.5rem; /* 6px */ + --inline-item-space-size: 0.5rem; /* 6px */ + --block-line-separation-size: 0.33rem; /* 10px */ + --inline-icon-space-size: 0.833rem; /* 10px */ + + /* Use for url and icon size */ + --block-url-label-size: 2rem; /* 24px */ + --inline-start-size: 1.66rem; /* 20px */ + --inline-button-size: 5.833rem; /* 70px */ + --icon-size: 1.166rem; /* 14px */ + + --small-text-size: 0.833rem; /* 10px */ + --small-radius: 3px; + --icon-button-size: calc(calc(var(--block-line-separation-size) * 2) + 1.66rem); /* 20px */ +} + +@media (min-resolution: 1dppx) { + html { + font-size: 14px; + } } *, @@ -19,6 +58,13 @@ html { box-sizing: inherit; } +form { + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; +} + table { border: 0; border-spacing: 0; @@ -35,6 +81,7 @@ table { } .scrollable { + border-block-start: 1px solid #f1f1f1; inline-size: 100%; max-block-size: 400px; overflow: auto; @@ -146,6 +193,10 @@ table { } /* Buttons */ +.button { + color: black; +} + .button.primary { background-color: #0996f8; color: white; @@ -216,7 +267,7 @@ table { .column-panel-content .button, .panel-footer .button { align-items: center; - block-size: 54px; + block-size: 100%; display: flex; flex: 1; justify-content: center; @@ -265,7 +316,7 @@ table { } .onboarding p { - color: #4a4a4a; + color: var(--text-normal-color); font-size: 14px; margin-block-end: 16px; max-inline-size: 84%; @@ -294,9 +345,10 @@ table { manage things like container crud */ .pop-button { align-items: center; - block-size: 48px; + block-size: var(--icon-button-size); + cursor: pointer; display: flex; - flex: 0 0 48px; + flex: 0 0 var(--icon-button-size); justify-content: center; } @@ -321,6 +373,10 @@ manage things like container crud */ .pop-button-image { block-size: 20px; flex: 0 0 20px; + margin-block-end: auto; + margin-block-start: auto; + margin-inline-end: auto; + margin-inline-start: auto; } .pop-button-image-small { @@ -332,18 +388,21 @@ manage things like container crud */ .panel-header { align-items: center; block-size: 48px; - border-block-end: 1px solid #ebebeb; display: flex; justify-content: space-between; } +.panel-header .usercontext-icon { + inline-size: var(--icon-button-size); +} + .column-panel-content .panel-header { flex: 0 0 48px; inline-size: 100%; } .panel-header-text { - color: #4a4a4a; + color: var(--text-normal-color); flex: 1; font-size: var(--font-size-heading); font-weight: normal; @@ -371,8 +430,31 @@ manage things like container crud */ text-transform: uppercase; } +.container-panel-controls { + display: flex; + justify-content: flex-end; + margin-block-end: var(--block-line-space-size); + margin-block-start: var(--block-line-space-size); + margin-inline-end: var(--inline-item-space-size); + margin-inline-start: var(--inline-item-space-size); +} + #container-panel #sort-containers-link { - margin-inline-end: 16px; + align-items: center; + block-size: var(--block-url-label-size); + border: 1px solid #d8d8d8; + border-radius: var(--small-radius); + color: var(--title-text-color); + display: flex; + font-size: var(--small-text-size); + inline-size: var(--inline-button-size); + justify-content: center; + text-decoration: none; +} + +#container-panel #sort-containers-link:hover, +#container-panel #sort-containers-link:focus { + background: #f2f2f2; } span ~ .panel-header-text { @@ -383,41 +465,75 @@ span ~ .panel-header-text { } #current-tab { + align-items: center; + color: var(--text-normal-color); + display: grid; + font-size: var(--small-text-size); + grid-column-gap: var(--inline-item-space-size); + grid-row-gap: var(--block-line-space-size); + grid-template-columns: var(--icon-size) var(--icon-size) 1fr; + margin-block-end: var(--block-line-space-size); + margin-block-start: var(--block-line-separation-size); + margin-inline-end: var(--inline-start-size); + margin-inline-start: var(--inline-start-size); max-inline-size: 100%; - min-block-size: 91px; - padding-block-end: 13px; - padding-block-start: 13px; - padding-inline-end: 16px; - padding-inline-start: 16px; +} + +#current-tab img { + max-block-size: var(--icon-size); } #current-tab > h3 { - color: #4a4a4a; - font-size: var(--font-size-heading); + color: var(--text-heading-color); font-weight: normal; - margin-block-end: 3px; + grid-column: span 3; + margin-block-end: 0; margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; } -#current-page > img { - block-size: 16px; - inline-size: 16px; +#current-page { + display: contents; +} + +#current-tab .page-title { + font-size: var(--font-size-heading); + grid-column: 2 / 4; } #current-tab > label { - align-items: center; - display: flex; - margin-inline-start: 17px; - white-space: nowrap; + display: contents; + font-size: var(--small-text-size); } #current-tab > label > input { - display: inline; + -moz-appearance: none; + block-size: var(--icon-size); + border: 1px solid #d8d8d8; + border-radius: var(--small-radius); + display: block; + grid-column-start: 2; + inline-size: var(--icon-size); + margin-block-end: 0; + margin-block-start: 0; + margin-inline-end: 0; + margin-inline-start: 0; +} + +#current-tab > label > input[disabled] { + background-color: #efefef; +} + +#current-tab > label > input:checked { + background-image: url("chrome://global/skin/in-content/check.svg#check-native"); + background-position: -1px -1px; + background-size: var(--icon-size); } #current-container { + color: var(--identity-tab-color); flex: 1; - text-transform: lowercase; } #current-tab > label > .usercontext-icon { @@ -434,7 +550,6 @@ span ~ .panel-header-text { .container-panel-row { align-items: center; background-color: #fefefe !important; - block-size: 48px; border-block-end: 1px solid #f1f1f1; box-sizing: border-box; display: flex; @@ -465,8 +580,9 @@ span ~ .panel-header-text { } .userContext-icon-wrapper { - block-size: 48px; - flex: 0 0 48px; + block-size: var(--icon-button-size); + flex: 0 0 var(--icon-button-size); + margin-inline-start: var(--inline-icon-space-size); } /* .userContext-icon is used natively, Bug 1333811 was raised to fix */ @@ -475,24 +591,28 @@ span ~ .panel-header-text { background-position: center center; background-repeat: no-repeat; background-size: 20px 20px; - block-size: 48px; + block-size: 100%; fill: var(--identity-icon-color); filter: url('/img/filters.svg#fill'); - flex: 0 0 48px; } .container-panel-row:hover .clickable .usercontext-icon, .container-panel-row:focus .clickable .usercontext-icon { background-image: url('/img/container-newtab.svg'); - fill: 'gray'; + fill: #979797; filter: url('/img/filters.svg#fill'); } +.container-panel-row .clickable:hover .usercontext-icon, +.container-panel-row .clickable:focus .usercontext-icon { + fill: #0094fb; +} + /* Panel Footer */ .panel-footer { align-items: center; background: #efefef; - block-size: 54px; + block-size: var(--icon-button-size); border-block-end: 1px solid #d8d8d8; color: #000; display: flex; @@ -501,14 +621,9 @@ span ~ .panel-header-text { justify-content: space-between; } -.panel-footer .pop-button { - block-size: 54px; - flex: 0 0 54px; -} - .edit-containers-text { align-items: center; - block-size: 54px; + block-size: 100%; border-inline-end: solid 1px #d8d8d8; display: flex; flex: 1; @@ -517,7 +632,7 @@ span ~ .panel-header-text { .edit-containers-text a { align-items: center; - block-size: 54px; + block-size: 100%; color: #0a0a0a; display: flex; flex: 1; @@ -525,8 +640,8 @@ span ~ .panel-header-text { } /* Container info list */ -#container-info-name { - margin-inline-end: 0.5rem; +.container-info-tab-title { + flex: 1; } #container-info-hideorshow { @@ -574,13 +689,16 @@ span ~ .panel-header-text { } .container-info-list { - border-block-start: 1px solid #ebebeb; display: flex; flex-direction: column; margin-block-start: 4px; padding-block-start: 4px; } +.container-info-list tbody { + display: contents; +} + .clickable { cursor: pointer; } @@ -631,6 +749,32 @@ span ~ .panel-header-text { padding-inline-start: 16px; } +#edit-sites-assigned { + flex: 1; +} + +#edit-sites-assigned h3 { + font-size: 14px; + font-weight: normal; + padding-block-end: 5px; + padding-inline-end: 16px; + padding-inline-start: 16px; +} + +#edit-sites-assigned table td { + display: flex; + padding-inline-end: 16px; + padding-inline-start: 16px; +} + +#edit-sites-assigned .delete-assignment { + display: none; +} + +#edit-sites-assigned tr:hover > td > .delete-assignment { + display: block; +} + .column-panel-content form span { align-items: center; block-size: 44px; @@ -679,6 +823,10 @@ span ~ .panel-header-text { padding-inline-start: 0; } +.edit-container-panel fieldset:last-of-type { + margin-block-end: 0; +} + .edit-container-panel input[type="text"] { block-size: 36px; border-radius: 3px; diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index 5c5038b..83a8435 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -5,7 +5,7 @@ async function load() { const currentCookieStoreId = searchParams.get("currentCookieStoreId"); const redirectUrlElement = document.getElementById("redirect-url"); redirectUrlElement.textContent = redirectUrl; - createFavicon(redirectUrl, redirectUrlElement); + appendFavicon(redirectUrl, redirectUrlElement); const container = await browser.contextualIdentities.get(cookieStoreId); [...document.querySelectorAll(".container-name")].forEach((containerNameElement) => { @@ -32,12 +32,11 @@ async function load() { }); } -function createFavicon(pageUrl, redirectUrlElement) { +function appendFavicon(pageUrl, redirectUrlElement) { const origin = new URL(pageUrl).origin; - const imageElement = document.createElement("img"); - imageElement.src = `${origin}/favicon.ico`; + const favIconElement = Utils.createFavIconElement(`${origin}/favicon.ico`); - redirectUrlElement.prepend(imageElement); + redirectUrlElement.prepend(favIconElement); } function confirmSubmit(redirectUrl, cookieStoreId) { diff --git a/webextension/js/content-script.js b/webextension/js/content-script.js new file mode 100644 index 0000000..ef6b4c4 --- /dev/null +++ b/webextension/js/content-script.js @@ -0,0 +1,53 @@ +async function delayAnimation(delay = 350) { + return new Promise((resolve) => { + setTimeout(resolve, delay); + }); +} + +async function doAnimation(element, property, value) { + return new Promise((resolve) => { + const handler = () => { + resolve(); + element.removeEventListener("transitionend", handler); + }; + element.addEventListener("transitionend", handler); + window.requestAnimationFrame(() => { + element.style[property] = value; + }); + }); +} +/* +async function awaitEvent(eventName) { + return new Promise((resolve) => { + const handler = () => { + resolve(); + divElement.removeEventListener(eventName, handler); + }; + divElement.addEventListener(eventName, handler); + }); +} +*/ + +async function addMessage(message) { + const divElement = document.createElement("div"); + divElement.classList.add("container-notification"); + // For the eager eyed, this is an experiment. It is however likely that a website will know it is "contained" anyway + divElement.innerText = message.text; + + const imageElement = document.createElement("img"); + imageElement.src = browser.extension.getURL("/img/container-site-d-24.png"); + divElement.prepend(imageElement); + + document.body.appendChild(divElement); + + await delayAnimation(100); + await doAnimation(divElement, "transform", "translateY(0)"); + await delayAnimation(2000); + await doAnimation(divElement, "transform", "translateY(-100%)"); + + divElement.remove(); +} + +browser.runtime.onMessage.addListener((message) => { + addMessage(message); +}); diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 12ca299..addd036 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -18,7 +18,6 @@ const P_CONTAINERS_EDIT = "containersEdit"; const P_CONTAINER_INFO = "containerInfo"; const P_CONTAINER_EDIT = "containerEdit"; const P_CONTAINER_DELETE = "containerDelete"; -const DEFAULT_FAVICON = "moz-icon://goat?size=16"; /** * Escapes any occurances of &, ", <, > or / with XML entities. @@ -119,7 +118,9 @@ const Logic = { }, addEnterHandler(element, handler) { - element.addEventListener("click", handler); + element.addEventListener("click", (e) => { + handler(e); + }); element.addEventListener("keydown", (e) => { if (e.keyCode === 13) { handler(e); @@ -208,6 +209,11 @@ const Logic = { return this._currentIdentity; }, + currentUserContextId() { + const identity = Logic.currentIdentity(); + return Logic.userContextId(identity.cookieStoreId); + }, + sendTelemetryPayload(message = {}) { if (!message.event) { throw new Error("Missing event name for telemetry"); @@ -234,10 +240,19 @@ const Logic = { }); }, - setOrRemoveAssignment(tab, value) { + getAssignmentObjectByContainer(userContextId) { + return browser.runtime.sendMessage({ + method: "getAssignmentObjectByContainer", + message: {userContextId} + }); + }, + + setOrRemoveAssignment(tabId, url, userContextId, value) { return browser.runtime.sendMessage({ method: "setOrRemoveAssignment", - tabId: tab.id, + tabId, + url, + userContextId, value }); }, @@ -437,7 +452,8 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const currentTabElement = document.getElementById("current-tab"); const assignmentCheckboxElement = document.getElementById("container-page-assigned"); assignmentCheckboxElement.addEventListener("change", () => { - Logic.setOrRemoveAssignment(currentTab, !assignmentCheckboxElement.checked); + const userContextId = Logic.userContextId(currentTab.cookieStoreId); + Logic.setOrRemoveAssignment(currentTab.id, currentTab.url, userContextId, !assignmentCheckboxElement.checked); }); currentTabElement.hidden = !currentTab; this.setupAssignmentCheckbox(false); @@ -446,29 +462,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, { const siteSettings = await Logic.getAssignment(currentTab); this.setupAssignmentCheckbox(siteSettings); const currentPage = document.getElementById("current-page"); - const favIconUrl = currentTab.favIconUrl || ""; - currentPage.innerHTML = escaped` - ${currentTab.title} - `; - - const imageElement = currentPage.querySelector("img"); - const loadListener = (e) => { - e.target.classList.remove("offpage"); - e.target.removeEventListener("load", loadListener); - e.target.removeEventListener("error", errorListener); - }; - const errorListener = (e) => { - e.target.src = DEFAULT_FAVICON; - }; - imageElement.addEventListener("error", errorListener); - imageElement.addEventListener("load", loadListener); + currentPage.innerHTML = escaped`${currentTab.title}`; + const favIconElement = Utils.createFavIconElement(currentTab.favIconUrl || ""); + currentPage.prepend(favIconElement); const currentContainer = document.getElementById("current-container"); currentContainer.innerText = identity.name; - const currentContainerIcon = document.getElementById("current-container-icon"); - currentContainerIcon.setAttribute("data-identity-icon", identity.icon); - currentContainerIcon.setAttribute("data-identity-color", identity.color); + currentContainer.setAttribute("data-identity-color", identity.color); } }, @@ -530,15 +531,21 @@ Logic.registerPanel(P_CONTAINERS_LIST, { }); }); - const list = document.querySelector(".identities-list"); + const list = document.querySelector(".identities-list tbody"); list.innerHTML = ""; list.appendChild(fragment); /* Not sure why extensions require a focus for the doorhanger, however it allows us to have a tabindex before the first selected item */ - document.addEventListener("focus", () => { + const focusHandler = () => { list.querySelector("tr").focus(); + document.removeEventListener("focus", focusHandler); + }; + document.addEventListener("focus", focusHandler); + /* If the user mousedown's first then remove the focus handler */ + document.addEventListener("mousedown", () => { + document.removeEventListener("focus", focusHandler); }); return Promise.resolve(); @@ -561,7 +568,7 @@ Logic.registerPanel(P_CONTAINER_INFO, { const identity = Logic.currentIdentity(); browser.runtime.sendMessage({ method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", - userContextId: Logic.userContextId(identity.cookieStoreId) + userContextId: Logic.currentUserContextId() }).then(() => { window.close(); }).catch(() => { @@ -633,7 +640,7 @@ Logic.registerPanel(P_CONTAINER_INFO, { // Let's retrieve the list of tabs. return browser.runtime.sendMessage({ method: "getTabs", - userContextId: Logic.userContextId(identity.cookieStoreId), + userContextId: Logic.currentUserContextId(), }).then(this.buildInfoTable); }, @@ -645,8 +652,9 @@ Logic.registerPanel(P_CONTAINER_INFO, { fragment.appendChild(tr); tr.classList.add("container-info-tab-row"); tr.innerHTML = escaped` - + ${tab.title}`; + tr.querySelector("td").appendChild(Utils.createFavIconElement(tab.favicon)); // On click, we activate this tab. But only if this tab is active. if (tab.active) { @@ -743,27 +751,19 @@ Logic.registerPanel(P_CONTAINER_EDIT, { this.initializeRadioButtons(); Logic.addEnterHandler(document.querySelector("#edit-container-panel-back-arrow"), () => { - Logic.showPreviousPanel(); + this._submitForm(); }); - - Logic.addEnterHandler(document.querySelector("#edit-container-cancel-link"), () => { - Logic.showPreviousPanel(); - }); - this._editForm = document.getElementById("edit-container-panel-form"); - const editLink = document.querySelector("#edit-container-ok-link"); - Logic.addEnterHandler(editLink, this._submitForm.bind(this)); - editLink.addEventListener("submit", this._submitForm.bind(this)); this._editForm.addEventListener("submit", this._submitForm.bind(this)); + }, _submitForm() { - const identity = Logic.currentIdentity(); const formValues = new FormData(this._editForm); return browser.runtime.sendMessage({ method: "createOrUpdateContainer", message: { - userContextId: Logic.userContextId(identity.cookieStoreId) || false, + userContextId: Logic.currentUserContextId() || false, params: { name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), icon: formValues.get("container-icon") || DEFAULT_ICON, @@ -779,6 +779,50 @@ Logic.registerPanel(P_CONTAINER_EDIT, { }); }, + showAssignedContainers(assignments) { + const assignmentPanel = document.getElementById("edit-sites-assigned"); + const assignmentKeys = Object.keys(assignments); + assignmentPanel.hidden = !(assignmentKeys.length > 0); + if (assignments) { + const tableElement = assignmentPanel.querySelector("table > tbody"); + /* Remove previous assignment list, + after removing one we rerender the list */ + while (tableElement.firstChild) { + tableElement.firstChild.remove(); + } + assignmentKeys.forEach((siteKey) => { + const site = assignments[siteKey]; + const trElement = document.createElement("tr"); + /* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load. + This is pending a better solution for favicons from web extensions */ + const assumedUrl = `https://${site.hostname}`; + trElement.innerHTML = escaped` + + ${site.hostname} + + `; + const deleteButton = trElement.querySelector(".delete-assignment"); + Logic.addEnterHandler(deleteButton, () => { + const userContextId = Logic.currentUserContextId(); + // Lets show the message to the current tab + // TODO remove then when firefox supports arrow fn async + Logic.currentTab().then((currentTab) => { + Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true); + delete assignments[siteKey]; + this.showAssignedContainers(assignments); + }).catch((e) => { + throw e; + }); + }); + trElement.classList.add("container-info-tab-row", "clickable"); + tableElement.appendChild(trElement); + }); + } + }, + initializeRadioButtons() { const colorRadioTemplate = (containerColor) => { return escaped` @@ -808,8 +852,13 @@ Logic.registerPanel(P_CONTAINER_EDIT, { }, // This method is called when the panel is shown. - prepare() { + async prepare() { const identity = Logic.currentIdentity(); + + const userContextId = Logic.currentUserContextId(); + const assignments = await Logic.getAssignmentObjectByContainer(userContextId); + this.showAssignedContainers(assignments); + document.querySelector("#edit-container-panel-name-input").value = identity.name || ""; [...document.querySelectorAll("[name='container-color']")].forEach(colorInput => { colorInput.checked = colorInput.value === identity.color; diff --git a/webextension/js/utils.js b/webextension/js/utils.js new file mode 100644 index 0000000..5d5046b --- /dev/null +++ b/webextension/js/utils.js @@ -0,0 +1,23 @@ +const DEFAULT_FAVICON = "moz-icon://goat?size=16"; + +// TODO use export here instead of globals +window.Utils = { + + createFavIconElement(url) { + const imageElement = document.createElement("img"); + imageElement.classList.add("icon", "offpage"); + imageElement.src = url; + const loadListener = (e) => { + e.target.classList.remove("offpage"); + e.target.removeEventListener("load", loadListener); + e.target.removeEventListener("error", errorListener); + }; + const errorListener = (e) => { + e.target.src = DEFAULT_FAVICON; + }; + imageElement.addEventListener("error", errorListener); + imageElement.addEventListener("load", loadListener); + return imageElement; + } + +}; diff --git a/webextension/manifest.json b/webextension/manifest.json index b99a5f3..d700e15 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -26,7 +26,6 @@ "contextualIdentities", "history", "idle", - "notifications", "storage", "tabs", "webRequestBlocking", @@ -36,7 +35,7 @@ "commands": { "_execute_browser_action": { "suggested_key": { - "default": "Ctrl+Y" + "default": "Ctrl+Period" }, "description": "Open containers panel" } @@ -54,5 +53,17 @@ "background": { "scripts": ["background.js"] - } + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["js/content-script.js"], + "css": ["css/content.css"] + } + ], + + "web_accessible_resources": [ + "/img/container-site-d-24.png" + ] } diff --git a/webextension/popup.html b/webextension/popup.html index 25a7d0d..b641895 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -3,6 +3,7 @@ Containers browserAction Popup + @@ -40,21 +41,21 @@

Current Tab

-
+
-
-

Containers

+
- - +
+
@@ -148,7 +156,7 @@
- +