diff --git a/index.js b/index.js index ba8dacb..2bc59e1 100644 --- a/index.js +++ b/index.js @@ -196,12 +196,11 @@ const ContainerService = { "moveTabsToWindow", "queryIdentities", "getIdentity", - "createIdentity", - "removeIdentity", - "updateIdentity", "getPreference", "sendTelemetryPayload", "getTheme", + "refreshNeeded", + "forgetIdentityAndRefresh", "checkIncompatibleAddons" ]; @@ -309,7 +308,7 @@ const ContainerService = { }, registerBackgroundConnection(api) { - // This is only used for theme and container deletion notifications + // This is only used for theme notifications api.browser.runtime.onConnect.addListener((port) => { this._onBackgroundConnectCallback = (message, topic) => { port.postMessage({ @@ -911,86 +910,6 @@ const ContainerService = { return Promise.resolve(identity ? this._convert(identity) : null); }, - createIdentity(args) { - this.sendTelemetryPayload({ - "event": "add-container", - }); - - for (let arg of [ "name", "color", "icon"]) { // eslint-disable-line prefer-const - if (!(arg in args)) { - return Promise.reject("createIdentity must be called with " + arg + " argument."); - } - } - - const color = this._fromNameToColor(args.color); - const icon = this._fromNameToIcon(args.icon); - - const identity = ContextualIdentityProxy.create(args.name, icon, color); - - this._identitiesState[identity.userContextId] = this._createIdentityState(); - - this._refreshNeeded().then(() => { - return this._convert(identity); - }).catch(() => { - return this._convert(identity); - }); - }, - - updateIdentity(args) { - if (!("userContextId" in args)) { - return Promise.reject("updateIdentity must be called with userContextId argument."); - } - - this.sendTelemetryPayload({ - "event": "edit-container", - "userContextId": args.userContextId - }); - - const identity = ContextualIdentityProxy.getIdentityFromId(args.userContextId); - for (let arg of [ "name", "color", "icon"]) { // eslint-disable-line prefer-const - if ((arg in args)) { - identity[arg] = args[arg]; - } - } - - const color = this._fromNameToColor(identity.color); - const icon = this._fromNameToIcon(identity.icon); - - const updated = ContextualIdentityProxy.update(args.userContextId, - identity.name, - icon, color); - - this._refreshNeeded().then(() => { - return updated; - }).catch(() => { - return updated; - }); - }, - - removeIdentity(args) { - const eventName = "delete-container"; - if (!("userContextId" in args)) { - return Promise.reject("removeIdentity must be called with userContextId argument."); - } - - this.sendTelemetryPayload({ - "event": eventName, - "userContextId": args.userContextId - }); - - const tabsToClose = []; - this._containerTabIterator(args.userContextId, tab => { - tabsToClose.push(tab); - }); - - return this._closeTabs(tabsToClose).then(() => { - const removed = ContextualIdentityProxy.remove(args.userContextId); - this.triggerBackgroundCallback({userContextId: args.userContextId}, eventName); - this._forgetIdentity(args.userContextId); - return this._refreshNeeded().then(() => removed ); - }); - }, - // Preferences getPreference(args) { @@ -1039,7 +958,7 @@ const ContainerService = { return this._windowMap.get(window); }, - _refreshNeeded() { + refreshNeeded() { return this._configureWindows(); }, @@ -1169,6 +1088,11 @@ const ContainerService = { // End-Of-Hack }, + forgetIdentityAndRefresh(args) { + this._forgetIdentity(args.userContextId); + return this.refreshNeeded(); + }, + _forgetIdentity(userContextId = 0) { for (let window of windows.browserWindows) { // eslint-disable-line prefer-const window = viewFor(window); diff --git a/webextension/background.js b/webextension/background.js index a3eac44..93bdd78 100644 --- a/webextension/background.js +++ b/webextension/background.js @@ -58,22 +58,22 @@ const assignManager = { } }, - init() { - browser.runtime.onMessage.addListener((neverAskMessage) => { - const pageUrl = neverAskMessage.pageUrl; - if (neverAskMessage.neverAsk === true) { - // If we have existing data and for some reason it hasn't been deleted etc lets update it - this.storageArea.get(pageUrl).then((siteSettings) => { - if (siteSettings) { - siteSettings.neverAsk = true; - this.storageArea.set(pageUrl, siteSettings); - } - }).catch((e) => { - throw e; - }); - } - }); + _neverAsk(m) { + const pageUrl = m.pageUrl; + if (m.neverAsk === true) { + // If we have existing data and for some reason it hasn't been deleted etc lets update it + this.storageArea.get(pageUrl).then((siteSettings) => { + if (siteSettings) { + siteSettings.neverAsk = true; + this.storageArea.set(pageUrl, siteSettings); + } + }).catch((e) => { + throw e; + }); + } + }, + init() { browser.contextMenus.onClicked.addListener((info, tab) => { const userContextId = this.getUserContextIdFromCookieStore(tab); // Mapping ${URL(info.pageUrl).hostname} to ${userContextId} @@ -97,8 +97,7 @@ const assignManager = { message: `Successfully ${actionName} site to always open in this container`, iconUrl: browser.extension.getURL("/img/onboarding-1.png") }); - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + backgroundLogic.sendTelemetryPayload({ event: `${actionName}-container-assignment`, userContextId: userContextId, }); @@ -218,14 +217,12 @@ const assignManager = { // 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}); - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + backgroundLogic.sendTelemetryPayload({ event: "auto-reload-page-in-container", userContextId: userContextId, }); } else { - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + backgroundLogic.sendTelemetryPayload({ event: "prompt-to-reload-page-in-container", userContextId: userContextId, }); @@ -240,6 +237,77 @@ const assignManager = { } }; + +const backgroundLogic = { + deleteContainer(userContextId) { + this.sendTelemetryPayload({ + event: "delete-container", + userContextId + }); + + const removeTabsPromise = this._containerTabs(userContextId).then((tabs) => { + const tabIds = tabs.map((tab) => tab.id); + return browser.tabs.remove(tabIds); + }); + + return new Promise((resolve) => { + removeTabsPromise.then(() => { + const removed = browser.contextualIdentities.remove(this.cookieStoreId(userContextId)); + removed.then(() => { + assignManager.deleteContainer(userContextId); + browser.runtime.sendMessage({ + method: "forgetIdentityAndRefresh" + }).then(() => { + resolve({done: true, userContextId}); + }).catch((e) => {throw e;}); + }).catch((e) => {throw e;}); + }).catch((e) => {throw e;}); + }); + }, + + createOrUpdateContainer(options) { + let donePromise; + if (options.userContextId) { + donePromise = browser.contextualIdentities.update( + this.cookieStoreId(options.userContextId), + options.params + ); + this.sendTelemetryPayload({ + event: "edit-container", + userContextId: options.userContextId + }); + } else { + donePromise = browser.contextualIdentities.create(options.params); + this.sendTelemetryPayload({ + event: "add-container" + }); + } + return donePromise.then(() => { + browser.runtime.sendMessage({ + method: "refreshNeeded" + }); + }); + }, + + sendTelemetryPayload(message = {}) { + if (!message.event) { + throw new Error("Missing event name for telemetry"); + } + message.method = "sendTelemetryPayload"; + browser.runtime.sendMessage(message); + }, + + cookieStoreId(userContextId) { + return `firefox-container-${userContextId}`; + }, + + _containerTabs(userContextId) { + return browser.tabs.query({ + cookieStoreId: this.cookieStoreId(userContextId) + }).catch((e) => {throw e;}); + }, +}; + const messageHandler = { // After the timer completes we assume it's a tab the user meant to keep open // We use this to catch redirected tabs that have just opened @@ -247,6 +315,23 @@ const messageHandler = { LAST_CREATED_TAB_TIMER: 2000, init() { + browser.runtime.onMessage.addListener((m) => { + let response; + + switch (m.method) { + case "deleteContainer": + response = backgroundLogic.deleteContainer(m.message.userContextId); + break; + case "createOrUpdateContainer": + response = backgroundLogic.createOrUpdateContainer(m.message); + break; + case "neverAsk": + assignManager._neverAsk(m); + break; + } + return response; + }); + // Handles messages from index.js const port = browser.runtime.connect(); port.onMessage.addListener(m => { @@ -254,9 +339,6 @@ const messageHandler = { case "lightweight-theme-changed": themeManager.update(m.message); break; - case "delete-container": - assignManager.deleteContainer(m.message.userContextId); - break; default: throw new Error(`Unhandled message type: ${m.message}`); } @@ -408,8 +490,7 @@ const tabPageCounter = { return; } if (why === "user-closed-tab" && this.counters[tabId].tab) { - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + backgroundLogic.sendTelemetryPayload({ event: "page-requests-completed-per-tab", userContextId: this.counters[tabId].tab.cookieStoreId, pageRequestCount: this.counters[tabId].tab.pageRequests @@ -418,8 +499,7 @@ const tabPageCounter = { // delete both the 'tab' and 'activity' counters delete this.counters[tabId]; } else if (why === "user-went-idle" && this.counters[tabId].activity) { - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + backgroundLogic.sendTelemetryPayload({ event: "page-requests-completed-per-activity", userContextId: this.counters[tabId].activity.cookieStoreId, pageRequestCount: this.counters[tabId].activity.pageRequests diff --git a/webextension/js/confirm-page.js b/webextension/js/confirm-page.js index feefba1..d54dd06 100644 --- a/webextension/js/confirm-page.js +++ b/webextension/js/confirm-page.js @@ -9,6 +9,7 @@ document.getElementById("redirect-form").addEventListener("submit", (e) => { // Sending neverAsk message to background to store for next time we see this process if (neverAsk) { browser.runtime.sendMessage({ + method: "neverAsk", neverAsk: true, pageUrl: redirectUrl }).then(() => { diff --git a/webextension/js/popup.js b/webextension/js/popup.js index 7ed2b50..68f5dc1 100644 --- a/webextension/js/popup.js +++ b/webextension/js/popup.js @@ -141,6 +141,25 @@ const Logic = { return this._currentIdentity; }, + sendTelemetryPayload(message = {}) { + if (!message.event) { + throw new Error("Missing event name for telemetry"); + } + message.method = "sendTelemetryPayload"; + browser.runtime.sendMessage(message); + }, + + removeIdentity(userContextId) { + if (!userContextId) { + return Promise.reject("removeIdentity must be called with userContextId argument."); + } + + return browser.runtime.sendMessage({ + method: "deleteContainer", + message: {userContextId} + }); + }, + generateIdentityName() { const defaultName = "Container #"; const ids = []; @@ -240,8 +259,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { }); document.querySelector("#edit-containers-link").addEventListener("click", () => { - browser.runtime.sendMessage({ - method: "sendTelemetryPayload", + Logic.sendTelemetryPayload({ event: "edit-containers" }); Logic.showPanel(P_CONTAINERS_EDIT); @@ -360,12 +378,12 @@ Logic.registerPanel(P_CONTAINER_INFO, { moveTabsEl.parentNode.insertBefore(fragment, moveTabsEl.nextSibling); } else { moveTabsEl.addEventListener("click", () => { - return browser.runtime.sendMessage({ + browser.runtime.sendMessage({ method: "moveTabsToWindow", userContextId: Logic.currentIdentity().userContextId, }).then(() => { window.close(); - }); + }).catch((e) => { throw e; }); }); } }).catch(() => { @@ -531,12 +549,16 @@ Logic.registerPanel(P_CONTAINER_EDIT, { _submitForm() { const identity = Logic.currentIdentity(); const formValues = new FormData(this._editForm); - browser.runtime.sendMessage({ - method: identity.userContextId ? "updateIdentity" : "createIdentity", - userContextId: identity.userContextId || 0, - name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), - icon: formValues.get("container-icon") || DEFAULT_ICON, - color: formValues.get("container-color") || DEFAULT_COLOR, + return browser.runtime.sendMessage({ + method: "createOrUpdateContainer", + message: { + userContextId: identity.userContextId || false, + params: { + name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), + icon: formValues.get("container-icon") || DEFAULT_ICON, + color: formValues.get("container-color") || DEFAULT_COLOR, + } + } }).then(() => { return Logic.refreshIdentities(); }).then(() => { @@ -603,10 +625,12 @@ Logic.registerPanel(P_CONTAINER_DELETE, { }); document.querySelector("#delete-container-ok-link").addEventListener("click", () => { - browser.runtime.sendMessage({ - method: "removeIdentity", - userContextId: Logic.currentIdentity().userContextId, - }).then(() => { + /* This promise wont resolve if the last tab was removed from the window. + as the message async callback stops listening, this isn't an issue for us however it might be in future + if you want to do anything post delete do it in the background script. + Browser console currently warns about not listening also. + */ + Logic.removeIdentity(Logic.currentIdentity().userContextId).then(() => { return Logic.refreshIdentities(); }).then(() => { Logic.showPreviousPanel(); diff --git a/webextension/manifest.json b/webextension/manifest.json index d8e094a..a9582a7 100644 --- a/webextension/manifest.json +++ b/webextension/manifest.json @@ -23,6 +23,7 @@ "activeTab", "cookies", "contextMenus", + "contextualIdentities", "history", "idle", "notifications",