From 11e3194208a9e83b291e5bf333b21e66f1e4c419 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 15:03:29 +0100 Subject: [PATCH 1/6] Fixing favicon loading for all icons. Part of #561 --- .eslintrc.js | 1 + webextension/confirm-page.html | 1 + webextension/js/confirm-page.js | 9 ++++----- webextension/js/popup.js | 23 +++++------------------ webextension/js/utils.js | 23 +++++++++++++++++++++++ webextension/popup.html | 2 +- 6 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 webextension/js/utils.js 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/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/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/popup.js b/webextension/js/popup.js index 12ca299..7c43e5d 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. @@ -446,22 +445,9 @@ 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; @@ -645,8 +631,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) { 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/popup.html b/webextension/popup.html index 25a7d0d..a524f7e 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -148,7 +148,7 @@ - + From 922c4645106f6994f559c9e4b60efdbe908f13f8 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 15:36:45 +0100 Subject: [PATCH 2/6] Fix first focus issue on opening browser action. Fixes #564 --- webextension/js/popup.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 7c43e5d..cac57b6 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -523,8 +523,14 @@ Logic.registerPanel(P_CONTAINERS_LIST, { /* 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(); From 3e398807016ae475ad67796ff3310c1dd47ccb5a Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Wed, 31 May 2017 16:12:07 +0100 Subject: [PATCH 3/6] Fixing truncation for info screen tabs. --- webextension/css/popup.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index ad2966e..bd524a1 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -529,6 +529,10 @@ span ~ .panel-header-text { margin-inline-end: 0.5rem; } +.container-info-tab-title { + flex: 1; +} + #container-info-hideorshow { margin-block-start: 4px; } From 3ba6a994b047005b7cf61fb600de8d1beebc311b Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Thu, 1 Jun 2017 03:28:02 +0100 Subject: [PATCH 4/6] Adding in content notification to look more browser like. --- webextension/background.js | 13 +++----- webextension/css/content.css | 22 +++++++++++++ webextension/js/content-script.js | 53 +++++++++++++++++++++++++++++++ webextension/manifest.json | 15 +++++++-- 4 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 webextension/css/content.css create mode 100644 webextension/js/content-script.js diff --git a/webextension/background.js b/webextension/background.js index ca01d89..6448a99 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -159,7 +159,7 @@ const assignManager = { //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 +192,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 +205,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`, @@ -432,7 +429,7 @@ const messageHandler = { 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": 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/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/manifest.json b/webextension/manifest.json index b99a5f3..19b99bc 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -26,7 +26,6 @@ "contextualIdentities", "history", "idle", - "notifications", "storage", "tabs", "webRequestBlocking", @@ -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" + ] } From 8f64918638dadb37e88f7427d3a15f8a41f90994 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 2 Jun 2017 03:23:11 +0100 Subject: [PATCH 5/6] Start fixing styles --- webextension/css/popup.css | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/webextension/css/popup.css b/webextension/css/popup.css index bd524a1..231f6a3 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -2,6 +2,7 @@ body { inline-size: 300px; max-inline-size: 300px; + font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif; } html { @@ -11,6 +12,18 @@ html { :root { --font-size-heading: 16px; --primary-action-color: #248aeb; + + --block-line-space-size: 6px; + --block-line-separation-size: 10px; + --inline-icon-space-size: 10px; + /* Use for url and icon size */ + --block-url-label-size: 24px; + --inline-start-size: 20px; + --inline-button-size: 70px; + + --title-text-color: #000; + --small-text-size: 10px; + --small-radius: 3p 3pxx; } *, @@ -373,6 +386,10 @@ manage things like container crud */ #container-panel #sort-containers-link { margin-inline-end: 16px; + inline-size: var(--inline-button-size); + color: var(--title-text-color); + font-size: var(--small-text-size); + border-radius: var(--small-radius); } span ~ .panel-header-text { @@ -502,13 +519,13 @@ span ~ .panel-header-text { } .panel-footer .pop-button { - block-size: 54px; - flex: 0 0 54px; + block-size: 100%; + flex: 0; } .edit-containers-text { align-items: center; - block-size: 54px; + block-size: 100%; border-inline-end: solid 1px #d8d8d8; display: flex; flex: 1; @@ -517,7 +534,7 @@ span ~ .panel-header-text { .edit-containers-text a { align-items: center; - block-size: 54px; + block-size: 100%; color: #0a0a0a; display: flex; flex: 1; From 2a4891846a916961996ca12306d7b694244c90fa Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 2 Jun 2017 14:29:20 +0100 Subject: [PATCH 6/6] WIP context menu for Alt+Shift+Click #518 --- webextension/background.js | 9 ++ webextension/css/content.css | 64 +++++++++++++ webextension/css/popup.css | 101 --------------------- webextension/css/user-context.css | 100 +++++++++++++++++++++ webextension/js/content-script.js | 143 ++++++++++++++++++++++++++++++ webextension/manifest.json | 6 +- webextension/popup.html | 1 + 7 files changed, 321 insertions(+), 103 deletions(-) create mode 100644 webextension/css/user-context.css diff --git a/webextension/background.js b/webextension/background.js index 6448a99..24e679d 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -408,6 +408,15 @@ const messageHandler = { let response; switch (m.method) { + case "getContainers": + response = browser.contextualIdentities.query({}); + break; + case "openTab": + response = browser.tabs.create({ + url: m.url, + cookieStoreId: m.cookieStoreId + }); + break; case "deleteContainer": response = backgroundLogic.deleteContainer(m.message.userContextId); break; diff --git a/webextension/css/content.css b/webextension/css/content.css index 885b0fb..a389ed6 100644 --- a/webextension/css/content.css +++ b/webextension/css/content.css @@ -20,3 +20,67 @@ inline-size: 16px; margin-inline-end: 3px; } + +#containers-menu { + background: #fcfcfc; + border: 1px solid #979797; + border-radius: 3px; + box-shadow: 3px 3px 5px #91919166; + font-family: Roboto, Noto, "San Francisco", Ubuntu, "Segoe UI", "Fira Sans", message-box, Arial, sans-serif; + inline-size: 7rem; + position: absolute; + padding: 0; + z-index: 9999999; + /* This is the area we allow users to move away from the menu without it closing + */ + margin: 4px; + -moz-user-select: none; +} + +#containers-menu > li:first-of-type { + border-radius: 3px 3px 0 0; +} + +#containers-menu > li:last-of-type { + border: none; + border-radius: 0 0 3px 3px; +} + +.dark-theme#containers-menu { + background: #2b2f38; +} + +#containers-menu > li { + align-items: center; + border-block-end: 1px solid #979797; + color: #434343; + display: flex; + margin: 0; + padding: 10px; +} + +#containers-menu > li > span { + flex: 1; + mask-image: linear-gradient(to left, transparent, black 1em); + overflow: hidden; + white-space: nowrap; +} + +.dark-theme#containers-menu li { + color: #92959b; +} + +#containers-menu li:hover, +#containers-menu li:focus { + outline: none; + cursor: pointer; + background-color: #f0f0f0; +} + +#containers-menu > li > .usercontext-icon { + background-size: 16px; + flex: 0 0 16px; + margin-inline-end: 6px; + max-block-size: 16px; + min-block-size: 16px; +} diff --git a/webextension/css/popup.css b/webextension/css/popup.css index 231f6a3..18a51b6 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -64,95 +64,6 @@ table { white-space: nowrap; } -/* Color and icon helpers */ -[data-identity-color="blue"] { - --identity-tab-color: #37adff; - --identity-icon-color: #37adff; -} - -[data-identity-color="turquoise"] { - --identity-tab-color: #00c79a; - --identity-icon-color: #00c79a; -} - -[data-identity-color="green"] { - --identity-tab-color: #51cd00; - --identity-icon-color: #51cd00; -} - -[data-identity-color="yellow"] { - --identity-tab-color: #ffcb00; - --identity-icon-color: #ffcb00; -} - -[data-identity-color="orange"] { - --identity-tab-color: #ff9f00; - --identity-icon-color: #ff9f00; -} - -[data-identity-color="red"] { - --identity-tab-color: #ff613d; - --identity-icon-color: #ff613d; -} - -[data-identity-color="pink"] { - --identity-tab-color: #ff4bda; - --identity-icon-color: #ff4bda; -} - -[data-identity-color="purple"] { - --identity-tab-color: #af51f5; - --identity-icon-color: #af51f5; -} - -[data-identity-icon="fingerprint"] { - --identity-icon: url("/img/usercontext.svg#fingerprint"); -} - -[data-identity-icon="briefcase"] { - --identity-icon: url("/img/usercontext.svg#briefcase"); -} - -[data-identity-icon="dollar"] { - --identity-icon: url("/img/usercontext.svg#dollar"); -} - -[data-identity-icon="cart"] { - --identity-icon: url("/img/usercontext.svg#cart"); -} - -[data-identity-icon="circle"] { - --identity-icon: url("/img/usercontext.svg#circle"); -} - -[data-identity-icon="food"] { - --identity-icon: url("/img/usercontext.svg#food"); -} - -[data-identity-icon="gift"] { - --identity-icon: url("/img/usercontext.svg#gift"); -} - -[data-identity-icon="vacation"] { - --identity-icon: url("/img/usercontext.svg#vacation"); -} - -[data-identity-icon="fruit"] { - --identity-icon: url("/img/usercontext.svg#fruit"); -} - -[data-identity-icon="pet"] { - --identity-icon: url("/img/usercontext.svg#pet"); -} - -[data-identity-icon="tree"] { - --identity-icon: url("/img/usercontext.svg#tree"); -} - -[data-identity-icon="chill"] { - --identity-icon: url("/img/usercontext.svg#chill"); -} - #current-tab [data-identity-icon="default-tab"] { background: center center no-repeat url("/img/blank-tab.svg"); fill: currentColor; @@ -486,18 +397,6 @@ span ~ .panel-header-text { flex: 0 0 48px; } -/* .userContext-icon is used natively, Bug 1333811 was raised to fix */ -.usercontext-icon { - background-image: var(--identity-icon); - background-position: center center; - background-repeat: no-repeat; - background-size: 20px 20px; - block-size: 48px; - 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'); diff --git a/webextension/css/user-context.css b/webextension/css/user-context.css new file mode 100644 index 0000000..7b5d799 --- /dev/null +++ b/webextension/css/user-context.css @@ -0,0 +1,100 @@ +/* Color and icon helpers */ +[data-identity-color="blue"] { + --identity-tab-color: #37adff; + --identity-icon-color: #37adff; +} + +[data-identity-color="turquoise"] { + --identity-tab-color: #00c79a; + --identity-icon-color: #00c79a; +} + +[data-identity-color="green"] { + --identity-tab-color: #51cd00; + --identity-icon-color: #51cd00; +} + +[data-identity-color="yellow"] { + --identity-tab-color: #ffcb00; + --identity-icon-color: #ffcb00; +} + +[data-identity-color="orange"] { + --identity-tab-color: #ff9f00; + --identity-icon-color: #ff9f00; +} + +[data-identity-color="red"] { + --identity-tab-color: #ff613d; + --identity-icon-color: #ff613d; +} + +[data-identity-color="pink"] { + --identity-tab-color: #ff4bda; + --identity-icon-color: #ff4bda; +} + +[data-identity-color="purple"] { + --identity-tab-color: #af51f5; + --identity-icon-color: #af51f5; +} + +[data-identity-icon="fingerprint"] { + --identity-icon: url("/img/usercontext.svg#fingerprint"); +} + +[data-identity-icon="briefcase"] { + --identity-icon: url("/img/usercontext.svg#briefcase"); +} + +[data-identity-icon="dollar"] { + --identity-icon: url("/img/usercontext.svg#dollar"); +} + +[data-identity-icon="cart"] { + --identity-icon: url("/img/usercontext.svg#cart"); +} + +[data-identity-icon="circle"] { + --identity-icon: url("/img/usercontext.svg#circle"); +} + +[data-identity-icon="food"] { + --identity-icon: url("/img/usercontext.svg#food"); +} + +[data-identity-icon="gift"] { + --identity-icon: url("/img/usercontext.svg#gift"); +} + +[data-identity-icon="vacation"] { + --identity-icon: url("/img/usercontext.svg#vacation"); +} + +[data-identity-icon="fruit"] { + --identity-icon: url("/img/usercontext.svg#fruit"); +} + +[data-identity-icon="pet"] { + --identity-icon: url("/img/usercontext.svg#pet"); +} + +[data-identity-icon="tree"] { + --identity-icon: url("/img/usercontext.svg#tree"); +} + +[data-identity-icon="chill"] { + --identity-icon: url("/img/usercontext.svg#chill"); +} + +/* .userContext-icon is used natively, Bug 1333811 was raised to fix */ +.usercontext-icon { + background-image: var(--identity-icon); + background-position: center center; + background-repeat: no-repeat; + background-size: 20px 20px; + block-size: 48px; + fill: var(--identity-icon-color); + filter: url('/img/filters.svg#fill'); + flex: 0 0 48px; +} diff --git a/webextension/js/content-script.js b/webextension/js/content-script.js index ef6b4c4..0e466f5 100644 --- a/webextension/js/content-script.js +++ b/webextension/js/content-script.js @@ -51,3 +51,146 @@ async function addMessage(message) { browser.runtime.onMessage.addListener((message) => { addMessage(message); }); + + +const menuClickHandler = { + menuElement: null, + lastUrl: null, + linkSelector:"a[href]", + + init() { + this.createMenu(); + + document.addEventListener("keydown", this); + document.addEventListener("click", this); + }, + + handleEvent(e) { + switch(e.type) { + case "keydown": + if (this.isMenuOpen()) { + if (e.key === "Escape") { + this.menuClose(); + } + } + if (e.altKey && (e.shiftKey || e.key === "Shift")) { + e.preventDefault(); + e.stopPropagation(); + this.addOpenListeners(); + } + break; + case "keyup": + if (e.altKey && (e.shiftKey || e.key === "Shift")) { + this.removeOpenListeners(); + } + break; + case "click": + if (this.isMenuOpen() && + (e.target.closest(this.linkSelector) || e.target.matches(this.linkSelector))) { + e.preventDefault(); + e.stopPropagation(); + } else { + this.menuClose(); + } + this.removeOpenListeners(); + break; + case "mousedown": + if (this.isMenuOpen()) { + if (!e.target.closest("#containers-menu")) { + this.menuClose(); + } + return; + } + if (e.target.closest(this.linkSelector) || + e.target.matches(this.linkSelector)) { + // Prevent text selection + e.preventDefault(); + e.stopPropagation(); + this.lastUrl = e.target.href; + this.showMenu(e); + } + /* + setTimeout(() => { + this.removeOpenListeners(); + }, 1000); + */ + break; + } + }, + + addOpenListeners() { + document.addEventListener("mousedown", this); + document.addEventListener("keyup", this); + }, + + removeOpenListeners() { + document.removeEventListener("mousedown", this); + document.removeEventListener("keyup", this); + }, + + isMenuOpen() { + return !this.menuElement.hidden; + }, + + menuOpen() { + this.menuElement.hidden = false; + }, + + menuClose() { + this.menuElement.hidden = true; + }, + + getContainers() { + return browser.runtime.sendMessage({ + method: "getContainers" + }); + }, + + async createMenu() { + if (this.menuElement) { + return this.menuElement; + } + const menuElement = document.createElement("ul"); + menuElement.id = "containers-menu"; + menuElement.hidden = true; + const containers = await this.getContainers(); + containers.forEach((container) => { + const containerElement = document.createElement("li"); + const spanElement = document.createElement("span"); + spanElement.innerText = container.name; + containerElement.appendChild(spanElement); + containerElement.setAttribute("tabindex", 0); + const iconElement = document.createElement("div"); + iconElement.classList.add("usercontext-icon"); + iconElement.setAttribute("data-identity-icon", container.icon); + iconElement.setAttribute("data-identity-color", container.color); + containerElement.prepend(iconElement); + menuElement.appendChild(containerElement); + containerElement.addEventListener("click", (e) => { + this.openContainer(container); + }); + }); + document.body.appendChild(menuElement); + this.menuElement = menuElement; + return menuElement; + }, + + async showMenu(e) { + const menuElement = await this.createMenu(); + this.menuOpen(); + menuElement.style.top = `${e.clientY}px`; + menuElement.style.left = `${e.clientX}px`; + menuElement.querySelector("div").focus(); + }, + + openContainer(container) { + return browser.runtime.sendMessage({ + method: "openTab", + url: this.lastUrl, + cookieStoreId: container.cookieStoreId + }); + } + +}; + +menuClickHandler.init(); diff --git a/webextension/manifest.json b/webextension/manifest.json index 19b99bc..5b84996 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -59,11 +59,13 @@ { "matches": [""], "js": ["js/content-script.js"], - "css": ["css/content.css"] + "css": ["css/user-context.css", "css/content.css"] } ], "web_accessible_resources": [ - "/img/container-site-d-24.png" + "/img/container-site-d-24.png", + "/img/usercontext.svg", + "/img/filters.svg" ] } diff --git a/webextension/popup.html b/webextension/popup.html index a524f7e..3f9bca3 100644 --- a/webextension/popup.html +++ b/webextension/popup.html @@ -2,6 +2,7 @@ Containers browserAction Popup +