From d61b4dd9217db24d787071c2d773cc079214855e Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Fri, 5 Jan 2018 13:01:43 +0100 Subject: [PATCH 01/12] Ignore JetBrains IDE files #1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 752eb58..c745683 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ README.html addon.env src/web-ext-artifacts/* + +# JetBrains IDE files +.idea From 9e9d94f2bf7ff5581f0d3ff9f0f7fb9fd1df5883 Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Fri, 5 Jan 2018 13:05:32 +0100 Subject: [PATCH 02/12] Pass the openerTabId when automatically opening tabs in containers the `openerTabId` can also be seen as the tab's parent. This is useful for extensions like https://github.com/piroor/treestyletab #1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent --- src/js/background/assignManager.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index f95023f..8386edf 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -144,7 +144,15 @@ const assignManager = { return {}; } - this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, tab.active, siteSettings.neverAsk); + this.reloadPageInContainer( + options.url, + userContextId, + siteSettings.userContextId, + tab.index + 1, + tab.active, + siteSettings.neverAsk, + tab.id + ); this.calculateContextMenu(tab); /* Removal of existing tabs: @@ -350,13 +358,13 @@ const assignManager = { }); }, - reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false) { + reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null) { const cookieStoreId = backgroundLogic.cookieStoreId(userContextId); const loadPage = browser.extension.getURL("confirm-page.html"); // False represents assignment is not permitted // 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, index, active}); + browser.tabs.create({url, cookieStoreId, index, active, openerTabId}); } else { let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`; let currentCookieStoreId; @@ -367,6 +375,7 @@ const assignManager = { browser.tabs.create({ url: confirmUrl, cookieStoreId: currentCookieStoreId, + openerTabId, index, active }).then(() => { From 5900b6a886c20fe7c9ffdc72f595568bcd1fe08b Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Fri, 5 Jan 2018 13:51:47 +0100 Subject: [PATCH 03/12] Set the openerTabId to a tab that won't be removed/closed The tab might be removed before we can create the tab making the parent invalid. #1065 - [Feature Request] Pass openerTabId when creating tabs in order to know the parent --- src/js/background/assignManager.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 8386edf..7040b4f 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -143,7 +143,10 @@ const assignManager = { || this.storageArea.isExempted(options.url, tab.id)) { return {}; } - + const removeTab = backgroundLogic.NEW_TAB_PAGES.has(tab.url) + || (messageHandler.lastCreatedTab + && messageHandler.lastCreatedTab.id === tab.id); + const openTabId = removeTab ? tab.openerTabId : tab.id; this.reloadPageInContainer( options.url, userContextId, @@ -151,7 +154,7 @@ const assignManager = { tab.index + 1, tab.active, siteSettings.neverAsk, - tab.id + openTabId ); this.calculateContextMenu(tab); @@ -166,9 +169,7 @@ const assignManager = { however they don't run on about:blank so this would likely be just as hacky. We capture the time the tab was created and close if it was within the timeout to try to capture pages which haven't had user interaction or history. */ - if (backgroundLogic.NEW_TAB_PAGES.has(tab.url) - || (messageHandler.lastCreatedTab - && messageHandler.lastCreatedTab.id === tab.id)) { + if (removeTab) { browser.tabs.remove(tab.id); } return { From 59b72290ccb0b20e1d41d371c0e9b9fcd2d3652a Mon Sep 17 00:00:00 2001 From: stoically <29637501+stoically@users.noreply.github.com> Date: Wed, 31 Jan 2018 05:12:46 +0100 Subject: [PATCH 04/12] Added assignment feature test Part of #1107 --- .travis.yml | 2 +- package.json | 8 +- src/popup.html | 2 +- test/.eslintrc.js | 16 ++++ test/browser.mock.js | 122 +++++++++++++++++++++++++++++++ test/features/assignment.test.js | 74 +++++++++++++++++++ test/helper.js | 41 +++++++++++ test/setup.js | 101 +++++++++++++++++++++++++ 8 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 test/.eslintrc.js create mode 100644 test/browser.mock.js create mode 100644 test/features/assignment.test.js create mode 100644 test/helper.js create mode 100644 test/setup.js diff --git a/.travis.yml b/.travis.yml index 4614306..2642444 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "6.1" + - "lts/*" notifications: irc: diff --git a/package.json b/package.json index ca8eb7d..39ecde3 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,18 @@ "dependencies": {}, "devDependencies": { "addons-linter": "^0.15.14", + "chai": "^4.1.2", "deploy-txp": "^1.0.7", "eslint": "^3.17.1", "eslint-plugin-no-unsanitized": "^2.0.0", "eslint-plugin-promise": "^3.4.0", "htmllint-cli": "^0.0.5", + "jsdom": "^11.6.2", "json": "^9.0.6", + "mocha": "^5.0.0", "npm-run-all": "^4.0.0", + "sinon": "^4.2.2", + "sinon-chai": "^2.14.0", "stylelint": "^7.9.0", "stylelint-config-standard": "^16.0.0", "stylelint-order": "^0.3.0", @@ -38,6 +43,7 @@ "lint:html": "htmllint *.html", "lint:js": "eslint .", "package": "rm -rf src/web-ext-artifacts && npm run build && mv src/web-ext-artifacts/firefox_multi-account_containers-*.zip addon.xpi", - "test": "npm run lint" + "test": "npm run lint && mocha ./test/setup.js test/**/*.test.js", + "test-watch": "mocha ./test/setup.js test/**/*.test.js --watch" } } diff --git a/src/popup.html b/src/popup.html index 8cde498..a28dd32 100644 --- a/src/popup.html +++ b/src/popup.html @@ -2,7 +2,7 @@ Multi-Account Containers - + diff --git a/test/.eslintrc.js b/test/.eslintrc.js new file mode 100644 index 0000000..0b29fac --- /dev/null +++ b/test/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + env: { + "node": true, + "mocha": true + }, + globals: { + "sinon": false, + "expect": false, + "nextTick": false, + "buildBackgroundDom": false, + "background": false, + "buildPopupDom": false, + "popup": false, + "helper": false + } +} diff --git a/test/browser.mock.js b/test/browser.mock.js new file mode 100644 index 0000000..1c71598 --- /dev/null +++ b/test/browser.mock.js @@ -0,0 +1,122 @@ +module.exports = () => { + const _storage = {}; + + // could maybe be replaced by https://github.com/acvetkov/sinon-chrome + const browserMock = { + _storage, + runtime: { + onMessage: { + addListener: sinon.stub(), + }, + sendMessage: sinon.stub().resolves(), + }, + webRequest: { + onBeforeRequest: { + addListener: sinon.stub() + }, + onCompleted: { + addListener: sinon.stub() + } + }, + windows: { + getCurrent: sinon.stub().resolves({}), + onFocusChanged: { + addListener: sinon.stub(), + } + }, + tabs: { + onActivated: { + addListener: sinon.stub() + }, + onCreated: { + addListener: sinon.stub() + }, + onUpdated: { + addListener: sinon.stub() + }, + sendMessage: sinon.stub(), + query: sinon.stub().resolves([{}]), + get: sinon.stub(), + create: sinon.stub().resolves({}), + remove: sinon.stub().resolves() + }, + history: { + deleteUrl: sinon.stub() + }, + storage: { + local: { + get: sinon.stub(), + set: sinon.stub() + } + }, + contextualIdentities: { + create: sinon.stub(), + get: sinon.stub(), + query: sinon.stub().resolves([]) + }, + contextMenus: { + create: sinon.stub(), + remove: sinon.stub(), + onClicked: { + addListener: sinon.stub() + } + }, + browserAction: { + setBadgeBackgroundColor: sinon.stub(), + setBadgeText: sinon.stub() + }, + extension: { + getURL: sinon.stub().returns("moz-extension://multi-account-containers/confirm-page.html") + } + }; + + // inmemory local storage + browserMock.storage.local = { + get: sinon.spy(async key => { + if (!key) { + return _storage; + } + let result = {}; + if (Array.isArray(key)) { + key.map(akey => { + if (typeof _storage[akey] !== "undefined") { + result[akey] = _storage[akey]; + } + }); + } else if (typeof key === "object") { + // TODO support nested objects + Object.keys(key).map(oKey => { + if (typeof _storage[oKey] !== "undefined") { + result[oKey] = _storage[oKey]; + } else { + result[oKey] = key[oKey]; + } + }); + } else { + result = _storage[key]; + } + return result; + }), + set: sinon.spy(async (key, value) => { + if (typeof key === "object") { + // TODO support nested objects + Object.keys(key).map(oKey => { + _storage[oKey] = key[oKey]; + }); + } else { + _storage[key] = value; + } + }), + remove: sinon.spy(async (key) => { + if (Array.isArray(key)) { + key.map(aKey => { + delete _storage[aKey]; + }); + } else { + delete _storage[key]; + } + }), + }; + + return browserMock; +}; diff --git a/test/features/assignment.test.js b/test/features/assignment.test.js new file mode 100644 index 0000000..f336962 --- /dev/null +++ b/test/features/assignment.test.js @@ -0,0 +1,74 @@ +describe("Assignment Feature", () => { + const activeTab = { + id: 1, + cookieStoreId: "firefox-container-1", + url: "http://example.com", + index: 0 + }; + beforeEach(async () => { + await helper.browser.initializeWithTab(activeTab); + }); + + describe("click the 'Always open in' checkbox in the popup", () => { + beforeEach(async () => { + // popup click to set assignment for activeTab.url + await helper.popup.clickElementById("container-page-assigned"); + }); + + describe("open new Tab with the assigned URL in the default container", () => { + const newTab = { + id: 2, + cookieStoreId: "firefox-default", + url: activeTab.url, + index: 1, + active: true + }; + beforeEach(async () => { + // new Tab opening activeTab.url in default container + await helper.browser.openNewTab(newTab); + }); + + it("should open the confirm page", async () => { + // should have created a new tab with the confirm page + background.browser.tabs.create.should.have.been.calledWith({ + url: "moz-extension://multi-account-containers/confirm-page.html?" + + `url=${encodeURIComponent(activeTab.url)}` + + `&cookieStoreId=${activeTab.cookieStoreId}`, + cookieStoreId: undefined, + index: 2, + active: true + }); + }); + + it("should remove the new Tab that got opened in the default container", () => { + background.browser.tabs.remove.should.have.been.calledWith(newTab.id); + }); + }); + + describe("click the 'Always open in' checkbox in the popup again", () => { + beforeEach(async () => { + // popup click to remove assignment for activeTab.url + await helper.popup.clickElementById("container-page-assigned"); + }); + + describe("open new Tab with the no longer assigned URL in the default container", () => { + const newTab = { + id: 3, + cookieStoreId: "firefox-default", + url: activeTab.url, + index: 3, + active: true + }; + beforeEach(async () => { + // new Tab opening activeTab.url in default container + await helper.browser.openNewTab(newTab); + }); + + it("should not open the confirm page", async () => { + // should not have created a new tab + background.browser.tabs.create.should.not.have.been.called; + }); + }); + }); + }); +}); diff --git a/test/helper.js b/test/helper.js new file mode 100644 index 0000000..a19750b --- /dev/null +++ b/test/helper.js @@ -0,0 +1,41 @@ +module.exports = { + browser: { + async initializeWithTab(tab) { + await buildBackgroundDom({ + beforeParse(window) { + window.browser.tabs.get.resolves(tab); + window.browser.tabs.query.resolves([tab]); + window.browser.contextualIdentities.get.resolves({ + cookieStoreId: tab.cookieStoreId + }); + } + }); + await buildPopupDom({ + beforeParse(window) { + window.browser.tabs.get.resolves(tab); + window.browser.tabs.query.resolves([tab]); + } + }); + }, + + async openNewTab(tab) { + background.browser.tabs.get.resolves(tab); + background.browser.webRequest.onBeforeRequest.addListener.yield({ + frameId: 0, + tabId: tab.id, + url: tab.url + }); + background.browser.tabs.onCreated.addListener.yield(tab); + await nextTick(); + } + }, + + popup: { + async clickElementById(id) { + const clickEvent = popup.document.createEvent("HTMLEvents"); + clickEvent.initEvent("click"); + popup.document.getElementById(id).dispatchEvent(clickEvent); + await nextTick(); + } + }, +}; diff --git a/test/setup.js b/test/setup.js new file mode 100644 index 0000000..9672bfc --- /dev/null +++ b/test/setup.js @@ -0,0 +1,101 @@ +if (!process.listenerCount("unhandledRejection")) { + // eslint-disable-next-line no-console + process.on("unhandledRejection", r => console.log(r)); +} +const jsdom = require("jsdom"); +const path = require("path"); +const chai = require("chai"); +const sinonChai = require("sinon-chai"); +global.sinon = require("sinon"); +global.expect = chai.expect; +chai.should(); +chai.use(sinonChai); +global.nextTick = () => { + return new Promise(resolve => { + setTimeout(() => { + process.nextTick(resolve); + }); + }); +}; + +global.helper = require("./helper"); +const browserMock = require("./browser.mock"); +const srcBasePath = path.resolve(path.join(__dirname, "..", "src")); +const srcJsBackgroundPath = path.join(srcBasePath, "js", "background"); +global.buildBackgroundDom = async (options = {}) => { + const dom = await jsdom.JSDOM.fromFile(path.join(srcJsBackgroundPath, "index.html"), { + runScripts: "dangerously", + resources: "usable", + virtualConsole: (new jsdom.VirtualConsole).sendTo(console), + beforeParse(window) { + window.browser = browserMock(); + window.fetch = sinon.stub().resolves({ + json: sinon.stub().resolves({}) + }); + + if (options.beforeParse) { + options.beforeParse(window); + } + } + }); + await new Promise(resolve => { + dom.window.document.addEventListener("DOMContentLoaded", resolve); + }); + await nextTick(); + + global.background = { + dom, + browser: dom.window.browser + }; +}; + +global.buildPopupDom = async (options = {}) => { + const dom = await jsdom.JSDOM.fromFile(path.join(srcBasePath, "popup.html"), { + runScripts: "dangerously", + resources: "usable", + virtualConsole: (new jsdom.VirtualConsole).sendTo(console), + beforeParse(window) { + window.browser = browserMock(); + window.browser.storage.local.set("browserActionBadgesClicked", []); + window.browser.storage.local.set("onboarding-stage", 5); + window.browser.storage.local.set("achievements", []); + window.browser.storage.local.set.resetHistory(); + window.fetch = sinon.stub().resolves({ + json: sinon.stub().resolves({}) + }); + + if (options.beforeParse) { + options.beforeParse(window); + } + } + }); + await new Promise(resolve => { + dom.window.document.addEventListener("DOMContentLoaded", resolve); + }); + await nextTick(); + dom.window.browser.runtime.sendMessage.resetHistory(); + + if (global.background) { + dom.window.browser.runtime.sendMessage = sinon.spy(function() { + global.background.browser.runtime.onMessage.addListener.yield(...arguments); + }); + } + + global.popup = { + dom, + document: dom.window.document, + browser: dom.window.browser + }; +}; + +global.afterEach(() => { + if (global.background) { + global.background.dom.window.close(); + delete global.background.dom; + } + + if (global.popup) { + global.popup.dom.window.close(); + delete global.popup.dom; + } +}); From e867e0ab5b65038e5cba924bb115dcb5f6a2e5b2 Mon Sep 17 00:00:00 2001 From: stoically Date: Fri, 9 Feb 2018 10:54:52 +0100 Subject: [PATCH 05/12] Add test for issue #940 --- test/helper.js | 8 ++++++-- test/issues/940.test.js | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/issues/940.test.js diff --git a/test/helper.js b/test/helper.js index a19750b..1ce13b5 100644 --- a/test/helper.js +++ b/test/helper.js @@ -18,14 +18,18 @@ module.exports = { }); }, - async openNewTab(tab) { + async openNewTab(tab, options = {isAsync: true}) { background.browser.tabs.get.resolves(tab); background.browser.webRequest.onBeforeRequest.addListener.yield({ frameId: 0, tabId: tab.id, - url: tab.url + url: tab.url, + requestId: options.requestId }); background.browser.tabs.onCreated.addListener.yield(tab); + if (!options.isAsync) { + return; + } await nextTick(); } }, diff --git a/test/issues/940.test.js b/test/issues/940.test.js new file mode 100644 index 0000000..cf9a1ab --- /dev/null +++ b/test/issues/940.test.js @@ -0,0 +1,43 @@ +describe("#940", () => { + describe("when other onBeforeRequestHandlers are faster and redirect with the same requestId", () => { + it("should not open two confirm pages", async () => { + // init + const activeTab = { + id: 1, + cookieStoreId: "firefox-container-1", + url: "http://example.com", + index: 0 + }; + await helper.browser.initializeWithTab(activeTab); + // assign the activeTab.url + await helper.popup.clickElementById("container-page-assigned"); + + // start request and don't await the requests at all + // so the second request below is actually comparable to an actual redirect that also fires immediately + const newTab = { + id: 2, + cookieStoreId: "firefox-default", + url: activeTab.url, + index: 1, + active: true + }; + helper.browser.openNewTab(newTab, { + requestId: 1, + isAsync: false + }); + + // other addon sees the same request + // and redirects to the https version of activeTab.url + // since it's a redirect the request has the same requestId + background.browser.webRequest.onBeforeRequest.addListener.yield({ + frameId: 0, + tabId: newTab.id, + url: "https://example.com", + requestId: 1 + }); + await nextTick(); + + background.browser.tabs.create.should.have.been.calledOnce; + }); + }); +}); From d5b469a8730ac6fb3743ba9f8b877d846cb6ce43 Mon Sep 17 00:00:00 2001 From: stoically Date: Fri, 9 Feb 2018 10:47:05 +0100 Subject: [PATCH 06/12] Cancel requests with the same requestId Prevents potential redirects from opening two tabs Closes #1114 --- src/js/background/assignManager.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 0051e09..86b9c3e 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -144,6 +144,21 @@ const assignManager = { return {}; } + // we decided to cancel the request at this point, register it as canceled request as early as possible + if (!this.canceledRequests[options.requestId]) { + this.canceledRequests[options.requestId] = true; + // register a cleanup for handled requestIds + // all relevant requests that come in that timeframe with the same requestId will be canceled + setTimeout(() => { + delete this.canceledRequests[options.requestId]; + }, 2000); + } else { + // if we see a request for the same requestId at this point then this is a redirect that we have to cancel to prevent opening two tabs + return { + cancel: true + }; + } + this.reloadPageInContainer(options.url, userContextId, siteSettings.userContextId, tab.index + 1, tab.active, siteSettings.neverAsk); this.calculateContextMenu(tab); @@ -174,6 +189,7 @@ const assignManager = { }); // Before a request is handled by the browser we decide if we should route through a different container + this.canceledRequests = {}; browser.webRequest.onBeforeRequest.addListener((options) => { return this.onBeforeRequest(options); },{urls: [""], types: ["main_frame"]}, ["blocking"]); From c061e869ba09954a371c9ba602d28582b4db5371 Mon Sep 17 00:00:00 2001 From: stoically <29637501+stoically@users.noreply.github.com> Date: Tue, 30 Jan 2018 22:34:00 +0100 Subject: [PATCH 07/12] Add management permission to manifest.json Part of #1095 --- src/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/manifest.json b/src/manifest.json index d5350e2..3963e24 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -26,6 +26,7 @@ "contextualIdentities", "history", "idle", + "management", "storage", "tabs", "webRequestBlocking", From 5082101affc4d41b8b597042c861204cd278113d Mon Sep 17 00:00:00 2001 From: stoically <29637501+stoically@users.noreply.github.com> Date: Tue, 30 Jan 2018 22:34:11 +0100 Subject: [PATCH 08/12] Allow webextensions with contextualIdentities permission to get assignment Closes #1095 --- src/js/background/messageHandler.js | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index 1971953..6e5fced 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -72,6 +72,42 @@ const messageHandler = { return response; }); + // Handles external messages from webextensions + const externalExtensionAllowed = {}; + browser.runtime.onMessageExternal.addListener(async (message, sender) => { + if (!externalExtensionAllowed[sender.id]) { + const extensionInfo = await browser.management.get(sender.id); + if (!extensionInfo.permissions.includes("contextualIdentities")) { + throw new Error("Missing contextualIdentities permission"); + } + externalExtensionAllowed[sender.id] = true; + } + let response; + switch (message.method) { + case "getAssignment": + if (typeof message.url === "undefined") { + throw new Error("Missing message.url"); + } + response = assignManager.storageArea.get(message.url); + break; + default: + throw new Error("Unknown message.method"); + } + return response; + }); + // Delete externalExtensionAllowed if add-on installs/updates; permissions might change + browser.management.onInstalled.addListener(extensionInfo => { + if (externalExtensionAllowed[extensionInfo.id]) { + delete externalExtensionAllowed[extensionInfo.id]; + } + }); + // Delete externalExtensionAllowed if add-on uninstalls; not needed anymore + browser.management.onUninstalled.addListener(extensionInfo => { + if (externalExtensionAllowed[extensionInfo.id]) { + delete externalExtensionAllowed[extensionInfo.id]; + } + }); + if (browser.contextualIdentities.onRemoved) { browser.contextualIdentities.onRemoved.addListener(({contextualIdentity}) => { const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(contextualIdentity.cookieStoreId); From 02ad95b86e0d34a6af69c643af3b429dbac33bf9 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 9 Feb 2018 18:33:54 +0000 Subject: [PATCH 09/12] Bumping version to 6.0.0 to account for latest fixes --- package.json | 2 +- src/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 39ecde3..a09f79e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "testpilot-containers", "title": "Multi-Account Containers", "description": "Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.", - "version": "5.0.1", + "version": "6.0.0", "author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston", "bugs": { "url": "https://github.com/mozilla/testpilot-containers/issues" diff --git a/src/manifest.json b/src/manifest.json index 3963e24..27c1f57 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Firefox Multi-Account Containers", - "version": "5.0.1", + "version": "6.0.0", "description": "Multi-Account Containers helps you keep all the parts of your online life contained in different tabs. Custom labels and color-coded tabs help keep different activities — like online shopping, travel planning, or checking work email — separate.", "icons": { From b784681eefa9d80781093ddf902ffcf2e8745469 Mon Sep 17 00:00:00 2001 From: Jonathan Kingston Date: Fri, 9 Feb 2018 18:37:29 +0000 Subject: [PATCH 10/12] Adding an s:// --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index def3d7d..7574cf0 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Finally, we also publish the release to GitHub for those followers. ### Links -Facebook & Twitter icons CC-Attrib http://fairheadcreative.com. +Facebook & Twitter icons CC-Attrib https://fairheadcreative.com. - [Licence](./LICENSE.txt) - [Contributing](./CONTRIBUTING.md) From 718652e211a0b56e338ebfedca743693acf8e092 Mon Sep 17 00:00:00 2001 From: stoically Date: Sat, 10 Feb 2018 00:50:29 +0100 Subject: [PATCH 11/12] Add tests for external webextensions feature --- test/browser.mock.js | 12 ++++ test/features/external-webextensions.test.js | 67 ++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/features/external-webextensions.test.js diff --git a/test/browser.mock.js b/test/browser.mock.js index 1c71598..5ae3de5 100644 --- a/test/browser.mock.js +++ b/test/browser.mock.js @@ -8,6 +8,9 @@ module.exports = () => { onMessage: { addListener: sinon.stub(), }, + onMessageExternal: { + addListener: sinon.stub(), + }, sendMessage: sinon.stub().resolves(), }, webRequest: { @@ -65,6 +68,15 @@ module.exports = () => { setBadgeBackgroundColor: sinon.stub(), setBadgeText: sinon.stub() }, + management: { + get: sinon.stub(), + onInstalled: { + addListener: sinon.stub() + }, + onUninstalled: { + addListener: sinon.stub() + } + }, extension: { getURL: sinon.stub().returns("moz-extension://multi-account-containers/confirm-page.html") } diff --git a/test/features/external-webextensions.test.js b/test/features/external-webextensions.test.js new file mode 100644 index 0000000..fe32e4d --- /dev/null +++ b/test/features/external-webextensions.test.js @@ -0,0 +1,67 @@ +describe("External Webextensions", () => { + const activeTab = { + id: 1, + cookieStoreId: "firefox-container-1", + url: "http://example.com", + index: 0 + }; + beforeEach(async () => { + await helper.browser.initializeWithTab(activeTab); + await helper.popup.clickElementById("container-page-assigned"); + }); + + describe("with contextualIdentities permissions", () => { + it("should be able to get assignments", async () => { + background.browser.management.get.resolves({ + permissions: ["contextualIdentities"] + }); + + const message = { + method: "getAssignment", + url: "http://example.com" + }; + const sender = { + id: "external-webextension" + }; + + // currently not possible to get the return value of yielding with sinon + // so we expect that if no error is thrown and the storage was called, everything is ok + // maybe i get around to provide a PR https://github.com/sinonjs/sinon/issues/903 + // + // the alternative would be to expose the actual messageHandler and call it directly + // but personally i think that goes against the black-box-ish nature of these feature tests + const rejectionStub = sinon.stub(); + process.on("unhandledRejection", rejectionStub); + background.browser.runtime.onMessageExternal.addListener.yield(message, sender); + await nextTick(); + process.removeListener("unhandledRejection", rejectionStub); + rejectionStub.should.not.have.been.called; + background.browser.storage.local.get.should.have.been.called; + }); + }); + + describe("without contextualIdentities permissions", () => { + it("should throw an error", async () => { + background.browser.management.get.resolves({ + permissions: [] + }); + + const message = { + method: "getAssignment", + url: "http://example.com" + }; + const sender = { + id: "external-webextension" + }; + + const rejectionStub = sinon.spy(); + process.on("unhandledRejection", rejectionStub); + background.browser.runtime.onMessageExternal.addListener.yield(message, sender); + await nextTick(); + process.removeListener("unhandledRejection", rejectionStub); + rejectionStub.should.have.been.calledWith(sinon.match({ + message: "Missing contextualIdentities permission" + })); + }); + }); +}); From 411f8b8f9a0f5f3a882730463bdecbf10d85c810 Mon Sep 17 00:00:00 2001 From: stoically Date: Sat, 10 Feb 2018 00:51:11 +0100 Subject: [PATCH 12/12] Fix assignment test to check for the new openerTabId property --- test/features/assignment.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/features/assignment.test.js b/test/features/assignment.test.js index f336962..632c60b 100644 --- a/test/features/assignment.test.js +++ b/test/features/assignment.test.js @@ -35,6 +35,7 @@ describe("Assignment Feature", () => { `url=${encodeURIComponent(activeTab.url)}` + `&cookieStoreId=${activeTab.cookieStoreId}`, cookieStoreId: undefined, + openerTabId: null, index: 2, active: true });