diff --git a/webextension/background.js b/webextension/background.js index e909c6b2c..5471e00a9 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -1,3 +1,172 @@ +const assignManager = { + closeableWindows: new Set([ + "about:startpage", + "about:newtab", + "about:home", + "about:blank" + ]), + menuAssignId: "open-in-this-container", + menuRemoveId: "remove-open-in-this-container", + storageArea: browser.storage.local, + + init() { + browser.tabs.onActivated.addListener((info) => { + browser.tabs.get(info.tabId).then((tab) => { + this.calculateContextMenu(tab); + }).catch((e) => { + throw e; + }); + }); + + browser.windows.onFocusChanged.addListener(() => { + browser.tabs.query({active: true}).then((tab) => { + this.calculateContextMenu(tab); + }).catch((e) => { + throw e; + }); + }); + + browser.runtime.onMessage.addListener((request) => { + if (request.neverAsk === true) { + this.storageArea.set({neverAsk: true}); + } + }); + + browser.contextMenus.onClicked.addListener((info, tab) => { + const pageUrl = new window.URL(info.pageUrl); + const userContextId = this.getUserContextIdFromCookieStore(tab); + // Mapping ${pageUrl.origin} to ${userContextId} + if (userContextId) { + const originStoreKey = this.getOriginStoreKey(pageUrl); + let actionName; + let storageAction; + if (info.menuItemId === this.menuAssignId) { + actionName = "added"; + storageAction = this.storageArea.set({[originStoreKey]: userContextId}); + } else { + actionName = "removed"; + storageAction = this.storageArea.remove([originStoreKey]); + } + storageAction.then(() => { + browser.notifications.create({ + type: "basic", + title: "Containers", + message: `Successfully ${actionName} ${pageUrl.origin} to always open in this container`, + iconUrl: browser.extension.getURL("/img/onboarding-1.png") + }); + this.calculateContextMenu(tab); + }).catch((e) => { + throw e; + }); + } + }); + + browser.webRequest.onBeforeRequest.addListener((options) => { + if (options.frameId !== 0 || options.tabId === -1) { + return {}; + } + const pageUrl = new window.URL(options.url); + const originStoreKey = this.getOriginStoreKey(pageUrl); + return Promise.all([ + browser.tabs.get(options.tabId), + this.storageArea.get([originStoreKey, "neverAsk"]) + ]).then(([tab, storage]) => { + const userContextId = this.getUserContextIdFromCookieStore(tab); + let neverAsk = false; + if (!(originStoreKey in storage)) { + return; + } + if ("neverAsk" in storage) { + neverAsk = storage.neverAsk; + } + const mapUserContextId = storage[originStoreKey]; + if (userContextId === mapUserContextId) { + return {}; + } + + this.reloadPageInContainer(options.url, mapUserContextId, tab.index, neverAsk); + this.calculateContextMenu(tab); + // If the user just opened the tab, we can auto close it + if (this.closeableWindows.has(tab.url)) { + browser.tabs.remove(tab.id); + } + return { + cancel: true, + }; + }).catch((e) => { + throw e; + }); + },{urls: [""], types: ["main_frame"]}, ["blocking"]); + + browser.webRequest.onCompleted.addListener((options) => { + if (options.frameId !== 0 || options.tabId === -1) { + return {}; + } + browser.tabs.get(options.tabId).then((tab) => { + this.calculateContextMenu(tab); + }).catch((e) => { + throw e; + }); + },{urls: [""], types: ["main_frame"]}); + }, + + getOriginStoreKey(url) { + const storagePrefix = "originContainerMap@@_"; + return `${storagePrefix}${url.origin}`; + }, + + getUserContextIdFromCookieStore(tab) { + if (!("cookieStoreId" in tab)) { + return false; + } + const cookieStore = tab.cookieStoreId; + const container = cookieStore.replace("firefox-container-", ""); + if (container !== cookieStore) { + return container; + } + return false; + }, + + calculateContextMenu(tab) { + const cookieStore = this.getUserContextIdFromCookieStore(tab); + browser.contextMenus.remove(this.menuAssignId); + browser.contextMenus.remove(this.menuRemoveId); + // Ensure we have a cookieStore to assign to + // Ensure we are not an important about url + if (cookieStore && !this.closeableWindows.has(tab.url)) { + const originStoreKey = this.getOriginStoreKey(new URL(tab.url)); + this.storageArea.get([originStoreKey]).then((store) => { + if (store[originStoreKey]) { + browser.contextMenus.create({ + id: this.menuRemoveId, + title: "Remove Open in This Container", + contexts: ["all"], + }); + return; + } + browser.contextMenus.create({ + id: this.menuAssignId, + title: "Always Open in This Container", + contexts: ["all"], + }); + }).catch((e) => { + throw e; + }); + } + }, + + reloadPageInContainer(url, userContextId, index, neverAsk = false) { + const loadPage = browser.extension.getURL("confirm-page.html"); + // If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there + if (neverAsk) { + browser.tabs.create({url, cookieStoreId: `firefox-container-${userContextId}`, index}); + } else { + browser.tabs.create({url: `${loadPage}?url=${url}`, cookieStoreId: `firefox-container-${userContextId}`, index}); + } + } +}; + +assignManager.init(); const themeManager = { existingTheme: null, diff --git a/webextension/confirm-page.html b/webextension/confirm-page.html new file mode 100644 index 000000000..018143d27 --- /dev/null +++ b/webextension/confirm-page.html @@ -0,0 +1,33 @@ + + + + Containers confirm navigation + + + + +
+
+

Hey, we heard you liked containers!

+
+
+

+ A page told us to open: +

+
+

+ You asked for to always open in this container, are you sure you want to confirm?
+

+

If you didn't mean to open this link or it looks suspicious don't open it.

+
+ +
+
+ +
+
+
+ + + + diff --git a/webextension/css/confirm-page.css b/webextension/css/confirm-page.css new file mode 100644 index 000000000..ac9102535 --- /dev/null +++ b/webextension/css/confirm-page.css @@ -0,0 +1,33 @@ +/* General Rules and Resets */ +.title { + background-image: none; +} + +main { + background: url(/img/onboarding-1.png) no-repeat; + background-position: -10px -15px; + background-size: 285px; + margin-inline-start: -285px; + padding-inline-start: 285px; +} + +@media only screen and (max-width: 1300px) { + main { + background: none; + } + + /* for a mid sized window we have enough for this but not our image */ + .title { + background-image: url("chrome://global/skin/icons/info.svg"); + } +} + +html { + box-sizing: border-box; + font: message-box; +} + +#redirect-url, +#redirect-origin { + font-weight: bold; +} diff --git a/webextension/css/popup.css b/webextension/css/popup.css index e2c8cd267..dd460d3ae 100644 --- a/webextension/css/popup.css +++ b/webextension/css/popup.css @@ -383,7 +383,7 @@ span ~ .panel-header-text { .panel-footer { align-items: center; background: #efefef; - block-size: 55px; + block-size: 54px; border-block-end: 1px solid #d8d8d8; color: #000; display: flex; diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js new file mode 100644 index 000000000..2bcd15898 --- /dev/null +++ b/webextension/js/confirm-page.js @@ -0,0 +1,25 @@ +const redirectUrl = new URL(window.location).searchParams.get("url"); +document.getElementById("redirect-url").textContent = redirectUrl; +const redirectOrigin = new URL(redirectUrl).origin; +document.getElementById("redirect-origin").textContent = redirectOrigin; + +document.getElementById("redirect-form").addEventListener("submit", (e) => { + e.preventDefault(); + const neverAsk = document.getElementById("never-ask").checked; + // Sending neverAsk message to background to store for next time we see this process + if (neverAsk) { + browser.runtime.sendMessage({ + neverAsk: true + }).then(() => { + redirect(); + }).catch(() => { + // Can't really do much here user will have to click it again + }); + } + redirect(); +}); + +function redirect() { + const redirectUrl = document.getElementById("redirect-url").textContent; + document.location.replace(redirectUrl); +} diff --git a/webextension/manifest.json b/webextension/manifest.json index 3bb95da8c..72b1fef6f 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -19,8 +19,15 @@ "homepage_url": "https://testpilot.firefox.com/", "permissions": [ + "", + "activeTab", "cookies", - "tabs" + "contextMenus", + "notifications", + "storage", + "tabs", + "webRequestBlocking", + "webRequest" ], "browser_action": {