Merge branch 'master' into master
This commit is contained in:
commit
38af2a385f
28 changed files with 1781 additions and 366 deletions
|
@ -19,7 +19,7 @@ For more info, see:
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
1. `npm install`
|
1. `npm install`
|
||||||
2. `./node_modules/.bin/web-ext run -s src/`
|
2. `./node_modules/web-ext/bin/web-ext run -s src/`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
`npm run test`
|
`npm run test`
|
||||||
|
|
13
package.json
13
package.json
|
@ -2,7 +2,7 @@
|
||||||
"name": "testpilot-containers",
|
"name": "testpilot-containers",
|
||||||
"title": "Multi-Account 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.",
|
"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": "6.1.1",
|
"version": "6.2.0",
|
||||||
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
|
"author": "Andrea Marchesini, Luke Crouch and Jonathan Kingston",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/mozilla/multi-account-containers/issues"
|
"url": "https://github.com/mozilla/multi-account-containers/issues"
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"addons-linter": "^1.3.2",
|
"addons-linter": "^1.3.2",
|
||||||
"ajv": "^6.6.2",
|
"ajv": "^6.6.3",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"eslint": "^6.6.0",
|
"eslint": "^6.6.0",
|
||||||
"eslint-plugin-no-unsanitized": "^2.0.0",
|
"eslint-plugin-no-unsanitized": "^2.0.0",
|
||||||
|
@ -19,14 +19,14 @@
|
||||||
"json": "^9.0.6",
|
"json": "^9.0.6",
|
||||||
"mocha": "^6.2.2",
|
"mocha": "^6.2.2",
|
||||||
"npm-run-all": "^4.0.0",
|
"npm-run-all": "^4.0.0",
|
||||||
"nyc": "^14.1.1",
|
"nyc": "^15.0.0",
|
||||||
"sinon": "^7.5.0",
|
"sinon": "^7.5.0",
|
||||||
"sinon-chai": "^3.3.0",
|
"sinon-chai": "^3.3.0",
|
||||||
"stylelint": "^7.9.0",
|
"stylelint": "^7.9.0",
|
||||||
"stylelint-config-standard": "^16.0.0",
|
"stylelint-config-standard": "^16.0.0",
|
||||||
"stylelint-order": "^0.3.0",
|
"stylelint-order": "^0.3.0",
|
||||||
"web-ext": "^2.9.3",
|
"web-ext": "^2.9.3",
|
||||||
"webextensions-jsdom": "^1.1.0"
|
"webextensions-jsdom": "^1.2.1"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/mozilla/multi-account-containers#readme",
|
"homepage": "https://github.com/mozilla/multi-account-containers#readme",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
@ -44,7 +44,8 @@
|
||||||
"lint:js": "eslint .",
|
"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",
|
"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 && npm run coverage",
|
"test": "npm run lint && npm run coverage",
|
||||||
"test-watch": "mocha ./test/setup.js test/**/*.test.js --watch",
|
"test:once": "mocha test/**/*.test.js",
|
||||||
"coverage": "nyc --reporter=html --reporter=text mocha ./test/setup.js test/**/*.test.js --timeout 60000"
|
"test:watch": "npm run test:once -- --watch",
|
||||||
|
"coverage": "nyc --reporter=html --reporter=text mocha test/**/*.test.js --timeout 60000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,6 +356,35 @@ table {
|
||||||
transition: background-color 75ms;
|
transition: background-color 75ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.half-button-wrapper {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 44px;
|
||||||
|
inline-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.half-onboarding-button {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #0996f8;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 44px;
|
||||||
|
inline-size: 50%;
|
||||||
|
justify-content: center;
|
||||||
|
margin-inline-end: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 75ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey-button {
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-button:hover,
|
.onboarding-button:hover,
|
||||||
.onboarding-button:active {
|
.onboarding-button:active {
|
||||||
background-color: #0675d3;
|
background-color: #0675d3;
|
||||||
|
|
1
src/img/Account.svg
Normal file
1
src/img/Account.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 160"><defs><style>.cls-1{fill:#6a57a5;}.cls-2{fill:#5a4a9e;}.cls-3{fill:#e7dfff;}</style></defs><title>account</title><path class="cls-1" d="M110,138.89A58.89,58.89,0,1,1,168.89,80,59,59,0,0,1,110,138.89Z"/><path class="cls-2" d="M110,130.27A50.27,50.27,0,1,1,160.27,80,50.33,50.33,0,0,1,110,130.27Z"/><circle class="cls-3" cx="110.39" cy="65.12" r="23.27" transform="translate(-12.01 27.1) rotate(-13.28)"/><path class="cls-3" d="M141.78,92.87c-8.2-9.46-19.58,3.28-31.39,3.28S87.2,83.41,79,92.87a7.83,7.83,0,0,0-.53,9.53,38.43,38.43,0,0,0,63.83,0A7.83,7.83,0,0,0,141.78,92.87Z"/></svg>
|
After Width: | Height: | Size: 676 B |
1
src/img/Sync.svg
Normal file
1
src/img/Sync.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 160"><defs><style>.cls-1{fill:#9f9fad;}.cls-2{fill:#5a4a9e;}.cls-3{fill:#6a57a5;}.cls-4{fill:#8f8f9d;}.cls-5{fill:none;stroke:#80808e;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.6px;}.cls-6{fill:#231f20;opacity:0.4;}.cls-7{fill:#ee3389;}.cls-8{fill:#7661aa;}</style></defs><title>Sync</title><path class="cls-1" d="M119.16,122.69v4.81H19.76v-4.81l12.83-3.21h72.15Z"/><rect class="cls-1" x="24.57" y="55.35" width="89.79" height="67.34" rx="3"/><path class="cls-2" d="M79.08,65l-49.7,49.7a1.61,1.61,0,0,0,1.6,1.61h77a1.62,1.62,0,0,0,1.61-1.61V65Z"/><polygon class="cls-3" points="29.38 64.97 29.38 114.67 79.08 64.97 29.38 64.97"/><path class="cls-2" d="M107.94,60.16H31a1.6,1.6,0,0,0-1.6,1.6V65h80.17V61.76A1.61,1.61,0,0,0,107.94,60.16Z"/><path class="cls-4" d="M108.74,121.09H30.18a.81.81,0,0,1,0-1.61h78.56a.81.81,0,1,1,0,1.61Z"/><line class="cls-5" x1="63.61" y1="124.18" x2="74.83" y2="124.18"/><path class="cls-6" d="M114.35,127.35H102.2V71.64a5.53,5.53,0,0,1,5.52-5.53h6.63Z"/><path class="cls-1" d="M200.24,134.72v4.81h-99.4v-4.81l12.82-3.21h72.15Z"/><rect class="cls-1" x="105.65" y="67.38" width="89.79" height="67.34" rx="3"/><path class="cls-2" d="M160.16,77l-49.71,49.7a1.61,1.61,0,0,0,1.61,1.6h77a1.6,1.6,0,0,0,1.6-1.6V77Z"/><polygon class="cls-3" points="110.45 77 110.45 126.7 160.16 77 110.45 77"/><path class="cls-2" d="M189,72.19h-77a1.61,1.61,0,0,0-1.61,1.6V77h80.17V73.79A1.6,1.6,0,0,0,189,72.19Z"/><path class="cls-4" d="M189.82,133.11H111.26a.8.8,0,1,1,0-1.6h78.56a.8.8,0,0,1,0,1.6Z"/><line class="cls-5" x1="144.69" y1="136.2" x2="155.91" y2="136.2"/><path class="cls-7" d="M136.85,50l-3-.55a3,3,0,0,0-3.51,2.37l-.27,1.45c-1.59,8.36-9.86,14.42-19.66,14.42a21,21,0,0,1-15.93-6.89H103a3,3,0,0,0,3-3v-3a3,3,0,0,0-3-3H84.86a3,3,0,0,0-3,3V73.64a3,3,0,0,0,3,3h3a3,3,0,0,0,3-3V69.72a30.8,30.8,0,0,0,19.57,6.87c14.15,0,26.15-9.11,28.54-21.66l.27-1.45A2.94,2.94,0,0,0,136.85,50Z"/><path class="cls-8" d="M84.06,47l3,.54a3.41,3.41,0,0,0,.55,0,3,3,0,0,0,3-2.41l.27-1.45h0c1.59-8.36,9.86-14.42,19.65-14.42a21,21,0,0,1,15.94,6.89H117.9a3,3,0,0,0-3,3v3a3,3,0,0,0,3,3h18.15a3,3,0,0,0,3-3V23.43a3,3,0,0,0-3-3h-3a3,3,0,0,0-3,3v3.92a30.82,30.82,0,0,0-19.58-6.88c-14.14,0-26.14,9.11-28.53,21.67l-.27,1.45A3,3,0,0,0,84.06,47Z"/></svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -7,6 +7,7 @@ module.exports = {
|
||||||
"badge": true,
|
"badge": true,
|
||||||
"backgroundLogic": true,
|
"backgroundLogic": true,
|
||||||
"identityState": true,
|
"identityState": true,
|
||||||
"messageHandler": true
|
"messageHandler": true,
|
||||||
|
"sync": true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const assignManager = {
|
window.assignManager = {
|
||||||
MENU_ASSIGN_ID: "open-in-this-container",
|
MENU_ASSIGN_ID: "open-in-this-container",
|
||||||
MENU_REMOVE_ID: "remove-open-in-this-container",
|
MENU_REMOVE_ID: "remove-open-in-this-container",
|
||||||
MENU_SEPARATOR_ID: "separator",
|
MENU_SEPARATOR_ID: "separator",
|
||||||
|
@ -9,8 +9,9 @@ const assignManager = {
|
||||||
area: browser.storage.local,
|
area: browser.storage.local,
|
||||||
exemptedTabs: {},
|
exemptedTabs: {},
|
||||||
|
|
||||||
getSiteStoreKey(pageUrl) {
|
getSiteStoreKey(pageUrlorUrlKey) {
|
||||||
const url = new window.URL(pageUrl);
|
if (pageUrlorUrlKey.includes("siteContainerMap@@_")) return pageUrlorUrlKey;
|
||||||
|
const url = new window.URL(pageUrlorUrlKey);
|
||||||
const storagePrefix = "siteContainerMap@@_";
|
const storagePrefix = "siteContainerMap@@_";
|
||||||
if (url.port === "80" || url.port === "443") {
|
if (url.port === "80" || url.port === "443") {
|
||||||
return `${storagePrefix}${url.hostname}`;
|
return `${storagePrefix}${url.hostname}`;
|
||||||
|
@ -19,29 +20,38 @@ const assignManager = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setExempted(pageUrl, tabId) {
|
setExempted(pageUrlorUrlKey, tabId) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
if (!(siteStoreKey in this.exemptedTabs)) {
|
if (!(siteStoreKey in this.exemptedTabs)) {
|
||||||
this.exemptedTabs[siteStoreKey] = [];
|
this.exemptedTabs[siteStoreKey] = [];
|
||||||
}
|
}
|
||||||
this.exemptedTabs[siteStoreKey].push(tabId);
|
this.exemptedTabs[siteStoreKey].push(tabId);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeExempted(pageUrl) {
|
removeExempted(pageUrlorUrlKey) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
this.exemptedTabs[siteStoreKey] = [];
|
this.exemptedTabs[siteStoreKey] = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
isExempted(pageUrl, tabId) {
|
isExempted(pageUrlorUrlKey, tabId) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
if (!(siteStoreKey in this.exemptedTabs)) {
|
if (!(siteStoreKey in this.exemptedTabs)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.exemptedTabs[siteStoreKey].includes(tabId);
|
return this.exemptedTabs[siteStoreKey].includes(tabId);
|
||||||
},
|
},
|
||||||
|
|
||||||
get(pageUrl) {
|
get(pageUrlorUrlKey) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
|
return this.getByUrlKey(siteStoreKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSyncEnabled() {
|
||||||
|
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||||
|
return !!syncEnabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
getByUrlKey(siteStoreKey) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.area.get([siteStoreKey]).then((storageResponse) => {
|
this.area.get([siteStoreKey]).then((storageResponse) => {
|
||||||
if (storageResponse && siteStoreKey in storageResponse) {
|
if (storageResponse && siteStoreKey in storageResponse) {
|
||||||
|
@ -54,51 +64,103 @@ const assignManager = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
set(pageUrl, data, exemptedTabIds) {
|
async set(pageUrlorUrlKey, data, exemptedTabIds, backup = true) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
if (exemptedTabIds) {
|
if (exemptedTabIds) {
|
||||||
exemptedTabIds.forEach((tabId) => {
|
exemptedTabIds.forEach((tabId) => {
|
||||||
this.setExempted(pageUrl, tabId);
|
this.setExempted(pageUrlorUrlKey, tabId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.area.set({
|
// eslint-disable-next-line require-atomic-updates
|
||||||
|
data.identityMacAddonUUID =
|
||||||
|
await identityState.lookupMACaddonUUID(data.userContextId);
|
||||||
|
await this.area.set({
|
||||||
[siteStoreKey]: data
|
[siteStoreKey]: data
|
||||||
});
|
});
|
||||||
|
const syncEnabled = await this.getSyncEnabled();
|
||||||
|
if (backup && syncEnabled) {
|
||||||
|
await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
|
||||||
|
}
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(pageUrl) {
|
async remove(pageUrlorUrlKey) {
|
||||||
const siteStoreKey = this.getSiteStoreKey(pageUrl);
|
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||||
// When we remove an assignment we should clear all the exemptions
|
// When we remove an assignment we should clear all the exemptions
|
||||||
this.removeExempted(pageUrl);
|
this.removeExempted(pageUrlorUrlKey);
|
||||||
return this.area.remove([siteStoreKey]);
|
await this.area.remove([siteStoreKey]);
|
||||||
|
const syncEnabled = await this.getSyncEnabled();
|
||||||
|
if (syncEnabled) await sync.storageArea.backup({siteStoreKey});
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteContainer(userContextId) {
|
async deleteContainer(userContextId) {
|
||||||
const sitesByContainer = await this.getByContainer(userContextId);
|
const sitesByContainer = await this.getAssignedSites(userContextId);
|
||||||
this.area.remove(Object.keys(sitesByContainer));
|
this.area.remove(Object.keys(sitesByContainer));
|
||||||
},
|
},
|
||||||
|
|
||||||
async getByContainer(userContextId) {
|
async getAssignedSites(userContextId = null) {
|
||||||
const sites = {};
|
const sites = {};
|
||||||
const siteConfigs = await this.area.get();
|
const siteConfigs = await this.area.get();
|
||||||
Object.keys(siteConfigs).forEach((key) => {
|
for(const urlKey of Object.keys(siteConfigs)) {
|
||||||
// For some reason this is stored as string... lets check them both as that
|
if (urlKey.includes("siteContainerMap@@_")) {
|
||||||
if (String(siteConfigs[key].userContextId) === String(userContextId)) {
|
// For some reason this is stored as string... lets check
|
||||||
const site = siteConfigs[key];
|
// them both as that
|
||||||
|
if (!!userContextId &&
|
||||||
|
String(siteConfigs[urlKey].userContextId)
|
||||||
|
!== String(userContextId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const site = siteConfigs[urlKey];
|
||||||
// In hindsight we should have stored this
|
// In hindsight we should have stored this
|
||||||
// TODO file a follow up to clean the storage onLoad
|
// TODO file a follow up to clean the storage onLoad
|
||||||
site.hostname = key.replace(/^siteContainerMap@@_/, "");
|
site.hostname = urlKey.replace(/^siteContainerMap@@_/, "");
|
||||||
sites[key] = site;
|
sites[urlKey] = site;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return sites;
|
return sites;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Looks for abandoned site assignments. If there is no identity with
|
||||||
|
* the site assignment's userContextId (cookieStoreId), then the assignment
|
||||||
|
* is removed.
|
||||||
|
*/
|
||||||
|
async upgradeData() {
|
||||||
|
const identitiesList = await browser.contextualIdentities.query({});
|
||||||
|
const macConfigs = await this.area.get();
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey.includes("siteContainerMap@@_")) {
|
||||||
|
const cookieStoreId =
|
||||||
|
"firefox-container-" + macConfigs[configKey].userContextId;
|
||||||
|
const match = identitiesList.find(
|
||||||
|
localIdentity => localIdentity.cookieStoreId === cookieStoreId
|
||||||
|
);
|
||||||
|
if (!match) {
|
||||||
|
await this.remove(configKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const updatedSiteAssignment = macConfigs[configKey];
|
||||||
|
updatedSiteAssignment.identityMacAddonUUID =
|
||||||
|
await identityState.lookupMACaddonUUID(match.cookieStoreId);
|
||||||
|
await this.set(
|
||||||
|
configKey,
|
||||||
|
updatedSiteAssignment,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_neverAsk(m) {
|
_neverAsk(m) {
|
||||||
const pageUrl = m.pageUrl;
|
const pageUrl = m.pageUrl;
|
||||||
if (m.neverAsk === true) {
|
if (m.neverAsk === true) {
|
||||||
// If we have existing data and for some reason it hasn't been deleted etc lets update it
|
// If we have existing data and for some reason it hasn't been
|
||||||
|
// deleted etc lets update it
|
||||||
this.storageArea.get(pageUrl).then((siteSettings) => {
|
this.storageArea.get(pageUrl).then((siteSettings) => {
|
||||||
if (siteSettings) {
|
if (siteSettings) {
|
||||||
siteSettings.neverAsk = true;
|
siteSettings.neverAsk = true;
|
||||||
|
@ -113,11 +175,12 @@ const assignManager = {
|
||||||
// We return here so the confirm page can load the tab when exempted
|
// We return here so the confirm page can load the tab when exempted
|
||||||
async _exemptTab(m) {
|
async _exemptTab(m) {
|
||||||
const pageUrl = m.pageUrl;
|
const pageUrl = m.pageUrl;
|
||||||
this.storageArea.setExempted(pageUrl, m.tabId);
|
await this.storageArea.setExempted(pageUrl, m.tabId);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Before a request is handled by the browser we decide if we should route through a different container
|
// Before a request is handled by the browser we decide if we should
|
||||||
|
// route through a different container
|
||||||
async onBeforeRequest(options) {
|
async onBeforeRequest(options) {
|
||||||
if (options.frameId !== 0 || options.tabId === -1) {
|
if (options.frameId !== 0 || options.tabId === -1) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -129,13 +192,14 @@ const assignManager = {
|
||||||
]);
|
]);
|
||||||
let container;
|
let container;
|
||||||
try {
|
try {
|
||||||
container = await browser.contextualIdentities.get(backgroundLogic.cookieStoreId(siteSettings.userContextId));
|
container = await browser.contextualIdentities
|
||||||
|
.get(backgroundLogic.cookieStoreId(siteSettings.userContextId));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
container = false;
|
container = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The container we have in the assignment map isn't present any more so lets remove it
|
// The container we have in the assignment map isn't present any
|
||||||
// then continue the existing load
|
// more so lets remove it then continue the existing load
|
||||||
if (siteSettings && !container) {
|
if (siteSettings && !container) {
|
||||||
this.deleteContainer(siteSettings.userContextId);
|
this.deleteContainer(siteSettings.userContextId);
|
||||||
return {};
|
return {};
|
||||||
|
@ -152,7 +216,8 @@ const assignManager = {
|
||||||
const openTabId = removeTab ? tab.openerTabId : tab.id;
|
const openTabId = removeTab ? tab.openerTabId : tab.id;
|
||||||
|
|
||||||
if (!this.canceledRequests[tab.id]) {
|
if (!this.canceledRequests[tab.id]) {
|
||||||
// we decided to cancel the request at this point, register canceled request
|
// we decided to cancel the request at this point, register
|
||||||
|
// canceled request
|
||||||
this.canceledRequests[tab.id] = {
|
this.canceledRequests[tab.id] = {
|
||||||
requestIds: {
|
requestIds: {
|
||||||
[options.requestId]: true
|
[options.requestId]: true
|
||||||
|
@ -162,8 +227,10 @@ const assignManager = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// since webRequest onCompleted and onErrorOccurred are not 100% reliable (see #1120)
|
// since webRequest onCompleted and onErrorOccurred are not 100%
|
||||||
// we register a timer here to cleanup canceled requests, just to make sure we don't
|
// reliable (see #1120)
|
||||||
|
// we register a timer here to cleanup canceled requests, just to
|
||||||
|
// make sure we don't
|
||||||
// end up in a situation where certain urls in a tab.id stay canceled
|
// end up in a situation where certain urls in a tab.id stay canceled
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.canceledRequests[tab.id]) {
|
if (this.canceledRequests[tab.id]) {
|
||||||
|
@ -175,10 +242,12 @@ const assignManager = {
|
||||||
if (this.canceledRequests[tab.id].requestIds[options.requestId] ||
|
if (this.canceledRequests[tab.id].requestIds[options.requestId] ||
|
||||||
this.canceledRequests[tab.id].urls[options.url]) {
|
this.canceledRequests[tab.id].urls[options.url]) {
|
||||||
// same requestId or url from the same tab
|
// same requestId or url from the same tab
|
||||||
// this is a redirect that we have to cancel early to prevent opening two tabs
|
// this is a redirect that we have to cancel early to prevent
|
||||||
|
// opening two tabs
|
||||||
cancelEarly = true;
|
cancelEarly = true;
|
||||||
}
|
}
|
||||||
// we decided to cancel the request at this point, register canceled request
|
// we decided to cancel the request at this point, register canceled
|
||||||
|
// request
|
||||||
this.canceledRequests[tab.id].requestIds[options.requestId] = true;
|
this.canceledRequests[tab.id].requestIds[options.requestId] = true;
|
||||||
this.canceledRequests[tab.id].urls[options.url] = true;
|
this.canceledRequests[tab.id].urls[options.url] = true;
|
||||||
if (cancelEarly) {
|
if (cancelEarly) {
|
||||||
|
@ -200,15 +269,27 @@ const assignManager = {
|
||||||
this.calculateContextMenu(tab);
|
this.calculateContextMenu(tab);
|
||||||
|
|
||||||
/* Removal of existing tabs:
|
/* Removal of existing tabs:
|
||||||
We aim to open the new assigned container tab / warning prompt in it's own tab:
|
We aim to open the new assigned container tab / warning prompt in
|
||||||
- As the history won't span from one container to another it seems most sane to not try and reopen a tab on history.back()
|
it's own tab:
|
||||||
- When users open a new tab themselves we want to make sure we don't end up with three tabs as per: https://github.com/mozilla/testpilot-containers/issues/421
|
- As the history won't span from one container to another it
|
||||||
If we are coming from an internal url that are used for the new tab page (NEW_TAB_PAGES), we can safely close as user is unlikely losing history
|
seems most sane to not try and reopen a tab on history.back()
|
||||||
Detecting redirects on "new tab" opening actions is pretty hard as we don't get tab history:
|
- When users open a new tab themselves we want to make sure we
|
||||||
- Redirects happen from Short URLs and tracking links that act as a gateway
|
don't end up with three tabs as per:
|
||||||
- Extensions don't provide a way to history crawl for tabs, we could inject content scripts to do this
|
https://github.com/mozilla/testpilot-containers/issues/421
|
||||||
however they don't run on about:blank so this would likely be just as hacky.
|
If we are coming from an internal url that are used for the new
|
||||||
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.
|
tab page (NEW_TAB_PAGES), we can safely close as user is unlikely
|
||||||
|
losing history
|
||||||
|
Detecting redirects on "new tab" opening actions is pretty hard
|
||||||
|
as we don't get tab history:
|
||||||
|
- Redirects happen from Short URLs and tracking links that act as
|
||||||
|
a gateway
|
||||||
|
- Extensions don't provide a way to history crawl for tabs, we
|
||||||
|
could inject content scripts to do this
|
||||||
|
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 (removeTab) {
|
if (removeTab) {
|
||||||
browser.tabs.remove(tab.id);
|
browser.tabs.remove(tab.id);
|
||||||
|
@ -220,10 +301,13 @@ const assignManager = {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
browser.contextMenus.onClicked.addListener((info, tab) => {
|
browser.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
info.bookmarkId ? this._onClickedBookmark(info) : this._onClickedHandler(info, tab);
|
info.bookmarkId ?
|
||||||
|
this._onClickedBookmark(info) :
|
||||||
|
this._onClickedHandler(info, tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Before a request is handled by the browser we decide if we should route through a different container
|
// Before a request is handled by the browser we decide if we should
|
||||||
|
// route through a different container
|
||||||
this.canceledRequests = {};
|
this.canceledRequests = {};
|
||||||
browser.webRequest.onBeforeRequest.addListener((options) => {
|
browser.webRequest.onBeforeRequest.addListener((options) => {
|
||||||
return this.onBeforeRequest(options);
|
return this.onBeforeRequest(options);
|
||||||
|
@ -240,25 +324,34 @@ const assignManager = {
|
||||||
delete this.canceledRequests[options.tabId];
|
delete this.canceledRequests[options.tabId];
|
||||||
}
|
}
|
||||||
},{urls: ["<all_urls>"], types: ["main_frame"]});
|
},{urls: ["<all_urls>"], types: ["main_frame"]});
|
||||||
|
|
||||||
this.resetBookmarksMenuItem();
|
this.resetBookmarksMenuItem();
|
||||||
},
|
},
|
||||||
|
|
||||||
async resetBookmarksMenuItem() {
|
async resetBookmarksMenuItem() {
|
||||||
const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]});
|
const hasPermission = await browser.permissions.contains({
|
||||||
|
permissions: ["bookmarks"]
|
||||||
|
});
|
||||||
if (this.hadBookmark === hasPermission) {
|
if (this.hadBookmark === hasPermission) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.hadBookmark = hasPermission;
|
this.hadBookmark = hasPermission;
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
this.initBookmarksMenu();
|
this.initBookmarksMenu();
|
||||||
browser.contextualIdentities.onCreated.addListener(this.contextualIdentityCreated);
|
browser.contextualIdentities.onCreated
|
||||||
browser.contextualIdentities.onUpdated.addListener(this.contextualIdentityUpdated);
|
.addListener(this.contextualIdentityCreated);
|
||||||
browser.contextualIdentities.onRemoved.addListener(this.contextualIdentityRemoved);
|
browser.contextualIdentities.onUpdated
|
||||||
|
.addListener(this.contextualIdentityUpdated);
|
||||||
|
browser.contextualIdentities.onRemoved
|
||||||
|
.addListener(this.contextualIdentityRemoved);
|
||||||
} else {
|
} else {
|
||||||
this.removeBookmarksMenu();
|
this.removeBookmarksMenu();
|
||||||
browser.contextualIdentities.onCreated.removeListener(this.contextualIdentityCreated);
|
browser.contextualIdentities.onCreated
|
||||||
browser.contextualIdentities.onUpdated.removeListener(this.contextualIdentityUpdated);
|
.removeListener(this.contextualIdentityCreated);
|
||||||
browser.contextualIdentities.onRemoved.removeListener(this.contextualIdentityRemoved);
|
browser.contextualIdentities.onUpdated
|
||||||
|
.removeListener(this.contextualIdentityUpdated);
|
||||||
|
browser.contextualIdentities.onRemoved
|
||||||
|
.removeListener(this.contextualIdentityRemoved);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -267,19 +360,25 @@ const assignManager = {
|
||||||
parentId: assignManager.OPEN_IN_CONTAINER,
|
parentId: assignManager.OPEN_IN_CONTAINER,
|
||||||
id: changeInfo.contextualIdentity.cookieStoreId,
|
id: changeInfo.contextualIdentity.cookieStoreId,
|
||||||
title: changeInfo.contextualIdentity.name,
|
title: changeInfo.contextualIdentity.name,
|
||||||
icons: { "16": `img/usercontext.svg#${changeInfo.contextualIdentity.icon}` }
|
icons: { "16": `img/usercontext.svg#${
|
||||||
|
changeInfo.contextualIdentity.icon
|
||||||
|
}` }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
contextualIdentityUpdated(changeInfo) {
|
contextualIdentityUpdated(changeInfo) {
|
||||||
browser.contextMenus.update(changeInfo.contextualIdentity.cookieStoreId, {
|
browser.contextMenus.update(
|
||||||
title: changeInfo.contextualIdentity.name,
|
changeInfo.contextualIdentity.cookieStoreId, {
|
||||||
icons: { "16": `img/usercontext.svg#${changeInfo.contextualIdentity.icon}` }
|
title: changeInfo.contextualIdentity.name,
|
||||||
});
|
icons: { "16": `img/usercontext.svg#${
|
||||||
|
changeInfo.contextualIdentity.icon}` }
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
contextualIdentityRemoved(changeInfo) {
|
contextualIdentityRemoved(changeInfo) {
|
||||||
browser.contextMenus.remove(changeInfo.contextualIdentity.cookieStoreId);
|
browser.contextMenus.remove(
|
||||||
|
changeInfo.contextualIdentity.cookieStoreId
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async _onClickedHandler(info, tab) {
|
async _onClickedHandler(info, tab) {
|
||||||
|
@ -295,7 +394,9 @@ const assignManager = {
|
||||||
} else {
|
} else {
|
||||||
remove = true;
|
remove = true;
|
||||||
}
|
}
|
||||||
await this._setOrRemoveAssignment(tab.id, info.pageUrl, userContextId, remove);
|
await this._setOrRemoveAssignment(
|
||||||
|
tab.id, info.pageUrl, userContextId, remove
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case this.MENU_MOVE_ID:
|
case this.MENU_MOVE_ID:
|
||||||
backgroundLogic.moveTabsToWindow({
|
backgroundLogic.moveTabsToWindow({
|
||||||
|
@ -316,17 +417,20 @@ const assignManager = {
|
||||||
async _onClickedBookmark(info) {
|
async _onClickedBookmark(info) {
|
||||||
|
|
||||||
async function _getBookmarksFromInfo(info) {
|
async function _getBookmarksFromInfo(info) {
|
||||||
const [bookmarkTreeNode] = await browser.bookmarks.get(info.bookmarkId);
|
const [bookmarkTreeNode] =
|
||||||
|
await browser.bookmarks.get(info.bookmarkId);
|
||||||
if (bookmarkTreeNode.type === "folder") {
|
if (bookmarkTreeNode.type === "folder") {
|
||||||
return await browser.bookmarks.getChildren(bookmarkTreeNode.id);
|
return browser.bookmarks.getChildren(bookmarkTreeNode.id);
|
||||||
}
|
}
|
||||||
return [bookmarkTreeNode];
|
return [bookmarkTreeNode];
|
||||||
}
|
}
|
||||||
|
|
||||||
const bookmarks = await _getBookmarksFromInfo(info);
|
const bookmarks = await _getBookmarksFromInfo(info);
|
||||||
for (const bookmark of bookmarks) {
|
for (const bookmark of bookmarks) {
|
||||||
// Some checks on the urls from https://github.com/Rob--W/bookmark-container-tab/ thanks!
|
// Some checks on the urls from
|
||||||
if ( !/^(javascript|place):/i.test(bookmark.url) && bookmark.type !== "folder") {
|
// https://github.com/Rob--W/bookmark-container-tab/ thanks!
|
||||||
|
if ( !/^(javascript|place):/i.test(bookmark.url) &&
|
||||||
|
bookmark.type !== "folder") {
|
||||||
const openInReaderMode = bookmark.url.startsWith("about:reader");
|
const openInReaderMode = bookmark.url.startsWith("about:reader");
|
||||||
if(openInReaderMode) {
|
if(openInReaderMode) {
|
||||||
try {
|
try {
|
||||||
|
@ -354,7 +458,9 @@ const assignManager = {
|
||||||
if (!("cookieStoreId" in tab)) {
|
if (!("cookieStoreId" in tab)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
|
return backgroundLogic.getUserContextIdFromCookieStoreId(
|
||||||
|
tab.cookieStoreId
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
isTabPermittedAssign(tab) {
|
isTabPermittedAssign(tab) {
|
||||||
|
@ -411,13 +517,13 @@ const assignManager = {
|
||||||
// Ensure we have a cookieStore to assign to
|
// Ensure we have a cookieStore to assign to
|
||||||
if (cookieStore
|
if (cookieStore
|
||||||
&& this.isTabPermittedAssign(tab)) {
|
&& this.isTabPermittedAssign(tab)) {
|
||||||
return await this.storageArea.get(tab.url);
|
return this.storageArea.get(tab.url);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getByContainer(userContextId) {
|
_getByContainer(userContextId) {
|
||||||
return this.storageArea.getByContainer(userContextId);
|
return this.storageArea.getAssignedSites(userContextId);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeContextMenu() {
|
removeContextMenu() {
|
||||||
|
@ -536,7 +642,7 @@ const assignManager = {
|
||||||
for (const identity of identities) {
|
for (const identity of identities) {
|
||||||
browser.contextMenus.remove(identity.cookieStoreId);
|
browser.contextMenus.remove(identity.cookieStoreId);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
assignManager.init();
|
assignManager.init();
|
||||||
|
|
|
@ -384,7 +384,7 @@ const backgroundLogic = {
|
||||||
containerState.hiddenTabs = [];
|
containerState.hiddenTabs = [];
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
return await identityState.storageArea.set(options.cookieStoreId, containerState);
|
return identityState.storageArea.set(options.cookieStoreId, containerState);
|
||||||
},
|
},
|
||||||
|
|
||||||
cookieStoreId(userContextId) {
|
cookieStoreId(userContextId) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const MAJOR_VERSIONS = ["2.3.0", "2.4.0"];
|
const MAJOR_VERSIONS = ["2.3.0", "2.4.0", "6.2.0"];
|
||||||
const badge = {
|
const badge = {
|
||||||
async init() {
|
async init() {
|
||||||
const currentWindow = await browser.windows.getCurrent();
|
const currentWindow = await browser.windows.getCurrent();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const identityState = {
|
window.identityState = {
|
||||||
storageArea: {
|
storageArea: {
|
||||||
area: browser.storage.local,
|
area: browser.storage.local,
|
||||||
|
|
||||||
|
@ -11,12 +11,23 @@ const identityState = {
|
||||||
const storeKey = this.getContainerStoreKey(cookieStoreId);
|
const storeKey = this.getContainerStoreKey(cookieStoreId);
|
||||||
const storageResponse = await this.area.get([storeKey]);
|
const storageResponse = await this.area.get([storeKey]);
|
||||||
if (storageResponse && storeKey in storageResponse) {
|
if (storageResponse && storeKey in storageResponse) {
|
||||||
|
if (!storageResponse[storeKey].macAddonUUID){
|
||||||
|
storageResponse[storeKey].macAddonUUID = uuidv4();
|
||||||
|
await this.set(cookieStoreId, storageResponse[storeKey]);
|
||||||
|
}
|
||||||
return storageResponse[storeKey];
|
return storageResponse[storeKey];
|
||||||
}
|
}
|
||||||
const defaultContainerState = identityState._createIdentityState();
|
// If local storage doesn't have an entry, look it up to make sure it's
|
||||||
await this.set(cookieStoreId, defaultContainerState);
|
// an in-use identity.
|
||||||
|
const identities = await browser.contextualIdentities.query({});
|
||||||
return defaultContainerState;
|
const match = identities.find(
|
||||||
|
(identity) => identity.cookieStoreId === cookieStoreId);
|
||||||
|
if (match) {
|
||||||
|
const defaultContainerState = identityState._createIdentityState();
|
||||||
|
await this.set(cookieStoreId, defaultContainerState);
|
||||||
|
return defaultContainerState;
|
||||||
|
}
|
||||||
|
throw new Error (`${cookieStoreId} not found`);
|
||||||
},
|
},
|
||||||
|
|
||||||
set(cookieStoreId, data) {
|
set(cookieStoreId, data) {
|
||||||
|
@ -26,9 +37,41 @@ const identityState = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
remove(cookieStoreId) {
|
async remove(cookieStoreId) {
|
||||||
const storeKey = this.getContainerStoreKey(cookieStoreId);
|
const storeKey = this.getContainerStoreKey(cookieStoreId);
|
||||||
return this.area.remove([storeKey]);
|
return this.area.remove([storeKey]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Looks for abandoned identity keys in local storage, and makes sure all
|
||||||
|
* identities registered in the browser are also in local storage. (this
|
||||||
|
* appears to not always be the case based on how this.get() is written)
|
||||||
|
*/
|
||||||
|
async upgradeData() {
|
||||||
|
const identitiesList = await browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
for (const identity of identitiesList) {
|
||||||
|
// ensure all identities have an entry in local storage
|
||||||
|
await identityState.addUUID(identity.cookieStoreId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const macConfigs = await this.area.get();
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey.includes("identitiesState@@_")) {
|
||||||
|
const cookieStoreId = String(configKey).replace(/^identitiesState@@_/, "");
|
||||||
|
const match = identitiesList.find(
|
||||||
|
localIdentity => localIdentity.cookieStoreId === cookieStoreId
|
||||||
|
);
|
||||||
|
if (cookieStoreId === "firefox-default") continue;
|
||||||
|
if (!match) {
|
||||||
|
await this.remove(cookieStoreId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!macConfigs[configKey].macAddonUUID) {
|
||||||
|
await identityState.storageArea.get(cookieStoreId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -36,6 +79,16 @@ const identityState = {
|
||||||
return Object.assign({}, tab);
|
return Object.assign({}, tab);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getCookieStoreIDuuidMap() {
|
||||||
|
const containers = {};
|
||||||
|
const identities = await browser.contextualIdentities.query({});
|
||||||
|
for(const identity of identities) {
|
||||||
|
const containerInfo = await this.storageArea.get(identity.cookieStoreId);
|
||||||
|
containers[identity.cookieStoreId] = containerInfo.macAddonUUID;
|
||||||
|
}
|
||||||
|
return containers;
|
||||||
|
},
|
||||||
|
|
||||||
async storeHidden(cookieStoreId, windowId) {
|
async storeHidden(cookieStoreId, windowId) {
|
||||||
const containerState = await this.storageArea.get(cookieStoreId);
|
const containerState = await this.storageArea.get(cookieStoreId);
|
||||||
const tabsByContainer = await browser.tabs.query({cookieStoreId, windowId});
|
const tabsByContainer = await browser.tabs.query({cookieStoreId, windowId});
|
||||||
|
@ -54,9 +107,57 @@ const identityState = {
|
||||||
return this.storageArea.set(cookieStoreId, containerState);
|
return this.storageArea.set(cookieStoreId, containerState);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async updateUUID(cookieStoreId, uuid) {
|
||||||
|
if (!cookieStoreId || !uuid) {
|
||||||
|
throw new Error ("cookieStoreId or uuid missing");
|
||||||
|
}
|
||||||
|
const containerState = await this.storageArea.get(cookieStoreId);
|
||||||
|
containerState.macAddonUUID = uuid;
|
||||||
|
await this.storageArea.set(cookieStoreId, containerState);
|
||||||
|
return uuid;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addUUID(cookieStoreId) {
|
||||||
|
await this.storageArea.get(cookieStoreId);
|
||||||
|
},
|
||||||
|
|
||||||
|
async lookupMACaddonUUID(cookieStoreId) {
|
||||||
|
// This stays a lookup, because if the cookieStoreId doesn't
|
||||||
|
// exist, this.get() will create it, which is not what we want.
|
||||||
|
const cookieStoreIdKey = cookieStoreId.includes("firefox-container-") ?
|
||||||
|
cookieStoreId : "firefox-container-" + cookieStoreId;
|
||||||
|
const macConfigs = await this.storageArea.area.get();
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey === this.storageArea.getContainerStoreKey(cookieStoreIdKey)) {
|
||||||
|
return macConfigs[configKey].macAddonUUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async lookupCookieStoreId(macAddonUUID) {
|
||||||
|
const macConfigs = await this.storageArea.area.get();
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey.includes("identitiesState@@_")) {
|
||||||
|
if(macConfigs[configKey].macAddonUUID === macAddonUUID) {
|
||||||
|
return String(configKey).replace(/^identitiesState@@_/, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
_createIdentityState() {
|
_createIdentityState() {
|
||||||
return {
|
return {
|
||||||
hiddenTabs: []
|
hiddenTabs: [],
|
||||||
|
macAddonUUID: uuidv4()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function uuidv4() {
|
||||||
|
// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
|
||||||
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||||
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -18,5 +18,6 @@
|
||||||
<script type="text/javascript" src="badge.js"></script>
|
<script type="text/javascript" src="badge.js"></script>
|
||||||
<script type="text/javascript" src="identityState.js"></script>
|
<script type="text/javascript" src="identityState.js"></script>
|
||||||
<script type="text/javascript" src="messageHandler.js"></script>
|
<script type="text/javascript" src="messageHandler.js"></script>
|
||||||
|
<script type="text/javascript" src="sync.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,6 +10,9 @@ const messageHandler = {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
switch (m.method) {
|
switch (m.method) {
|
||||||
|
case "resetSync":
|
||||||
|
response = sync.resetSync();
|
||||||
|
break;
|
||||||
case "resetBookmarksContext":
|
case "resetBookmarksContext":
|
||||||
response = assignManager.resetBookmarksMenuItem();
|
response = assignManager.resetBookmarksMenuItem();
|
||||||
break;
|
break;
|
||||||
|
|
580
src/js/background/sync.js
Normal file
580
src/js/background/sync.js
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
const SYNC_DEBUG = false;
|
||||||
|
|
||||||
|
const sync = {
|
||||||
|
storageArea: {
|
||||||
|
area: browser.storage.sync,
|
||||||
|
|
||||||
|
async get(){
|
||||||
|
return this.area.get();
|
||||||
|
},
|
||||||
|
|
||||||
|
async set(options) {
|
||||||
|
return this.area.set(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteIdentity(deletedIdentityUUID) {
|
||||||
|
const deletedIdentityList =
|
||||||
|
await sync.storageArea.getDeletedIdentityList();
|
||||||
|
if (
|
||||||
|
! deletedIdentityList.find(element => element === deletedIdentityUUID)
|
||||||
|
) {
|
||||||
|
deletedIdentityList.push(deletedIdentityUUID);
|
||||||
|
await sync.storageArea.set({ deletedIdentityList });
|
||||||
|
}
|
||||||
|
await this.removeIdentityKeyFromSync(deletedIdentityUUID);
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeIdentityKeyFromSync(deletedIdentityUUID) {
|
||||||
|
await sync.storageArea.area.remove( "identity@@_" + deletedIdentityUUID);
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteSite(siteStoreKey) {
|
||||||
|
const deletedSiteList =
|
||||||
|
await sync.storageArea.getDeletedSiteList();
|
||||||
|
if (deletedSiteList.find(element => element === siteStoreKey)) return;
|
||||||
|
deletedSiteList.push(siteStoreKey);
|
||||||
|
await sync.storageArea.set({ deletedSiteList });
|
||||||
|
await sync.storageArea.area.remove(siteStoreKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getDeletedIdentityList() {
|
||||||
|
const storedArray = await this.getStoredItem("deletedIdentityList");
|
||||||
|
return storedArray || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async getIdentities() {
|
||||||
|
const allSyncStorage = await this.get();
|
||||||
|
const identities = [];
|
||||||
|
for (const storageKey of Object.keys(allSyncStorage)) {
|
||||||
|
if (storageKey.includes("identity@@_")) {
|
||||||
|
identities.push(allSyncStorage[storageKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identities;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getDeletedSiteList() {
|
||||||
|
const storedArray = await this.getStoredItem("deletedSiteList");
|
||||||
|
return (storedArray) ? storedArray : [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAssignedSites() {
|
||||||
|
const allSyncStorage = await this.get();
|
||||||
|
const sites = {};
|
||||||
|
for (const storageKey of Object.keys(allSyncStorage)) {
|
||||||
|
if (storageKey.includes("siteContainerMap@@_")) {
|
||||||
|
sites[storageKey] = allSyncStorage[storageKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sites;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getStoredItem(objectKey) {
|
||||||
|
const outputObject = await this.get(objectKey);
|
||||||
|
if (outputObject && outputObject[objectKey])
|
||||||
|
return outputObject[objectKey];
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAllInstanceInfo() {
|
||||||
|
const instanceList = {};
|
||||||
|
const allSyncInfo = await this.get();
|
||||||
|
for (const objectKey of Object.keys(allSyncInfo)) {
|
||||||
|
if (objectKey.includes("MACinstance")) {
|
||||||
|
instanceList[objectKey] = allSyncInfo[objectKey]; }
|
||||||
|
}
|
||||||
|
return instanceList;
|
||||||
|
},
|
||||||
|
|
||||||
|
getInstanceKey() {
|
||||||
|
return browser.runtime.getURL("")
|
||||||
|
.replace(/moz-extension:\/\//, "MACinstance:")
|
||||||
|
.replace(/\//, "");
|
||||||
|
},
|
||||||
|
async removeInstance(installUUID) {
|
||||||
|
if (SYNC_DEBUG) console.log("removing", installUUID);
|
||||||
|
await this.area.remove(installUUID);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeThisInstanceFromSync() {
|
||||||
|
const installUUID = this.getInstanceKey();
|
||||||
|
await this.removeInstance(installUUID);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
async hasSyncStorage(){
|
||||||
|
const inSync = await this.get();
|
||||||
|
return !(Object.entries(inSync).length === 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
async backup(options) {
|
||||||
|
// remove listeners to avoid an infinite loop!
|
||||||
|
await sync.checkForListenersMaybeRemove();
|
||||||
|
|
||||||
|
const identities = await updateSyncIdentities();
|
||||||
|
const siteAssignments = await updateSyncSiteAssignments();
|
||||||
|
await updateInstanceInfo(identities, siteAssignments);
|
||||||
|
if (options && options.uuid)
|
||||||
|
await this.deleteIdentity(options.uuid);
|
||||||
|
if (options && options.undeleteUUID)
|
||||||
|
await removeFromDeletedIdentityList(options.undeleteUUID);
|
||||||
|
if (options && options.siteStoreKey)
|
||||||
|
await this.deleteSite(options.siteStoreKey);
|
||||||
|
if (options && options.undeleteSiteStoreKey)
|
||||||
|
await removeFromDeletedSitesList(options.undeleteSiteStoreKey);
|
||||||
|
|
||||||
|
if (SYNC_DEBUG) console.log("Backed up!");
|
||||||
|
await sync.checkForListenersMaybeAdd();
|
||||||
|
|
||||||
|
async function updateSyncIdentities() {
|
||||||
|
const identities = await browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
for (const identity of identities) {
|
||||||
|
delete identity.colorCode;
|
||||||
|
delete identity.iconUrl;
|
||||||
|
identity.macAddonUUID = await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
||||||
|
if(identity.macAddonUUID) {
|
||||||
|
const storageKey = "identity@@_" + identity.macAddonUUID;
|
||||||
|
await sync.storageArea.set({ [storageKey]: identity });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//await sync.storageArea.set({ identities });
|
||||||
|
return identities;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSyncSiteAssignments() {
|
||||||
|
const assignedSites =
|
||||||
|
await assignManager.storageArea.getAssignedSites();
|
||||||
|
for (const siteKey of Object.keys(assignedSites)) {
|
||||||
|
await sync.storageArea.set({ [siteKey]: assignedSites[siteKey] });
|
||||||
|
}
|
||||||
|
return assignedSites;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateInstanceInfo(identitiesInput, siteAssignmentsInput) {
|
||||||
|
const date = new Date();
|
||||||
|
const timestamp = date.getTime();
|
||||||
|
const installUUID = sync.storageArea.getInstanceKey();
|
||||||
|
if (SYNC_DEBUG) console.log("adding", installUUID);
|
||||||
|
const identities = [];
|
||||||
|
const siteAssignments = [];
|
||||||
|
for (const identity of identitiesInput) {
|
||||||
|
identities.push(identity.macAddonUUID);
|
||||||
|
}
|
||||||
|
for (const siteAssignmentKey of Object.keys(siteAssignmentsInput)) {
|
||||||
|
siteAssignments.push(siteAssignmentKey);
|
||||||
|
}
|
||||||
|
await sync.storageArea.set({ [installUUID]: { timestamp, identities, siteAssignments } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeFromDeletedIdentityList(identityUUID) {
|
||||||
|
const deletedIdentityList =
|
||||||
|
await sync.storageArea.getDeletedIdentityList();
|
||||||
|
const newDeletedIdentityList = deletedIdentityList
|
||||||
|
.filter(element => element !== identityUUID);
|
||||||
|
await sync.storageArea.set({ deletedIdentityList: newDeletedIdentityList });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeFromDeletedSitesList(siteStoreKey) {
|
||||||
|
const deletedSiteList =
|
||||||
|
await sync.storageArea.getDeletedSiteList();
|
||||||
|
const newDeletedSiteList = deletedSiteList
|
||||||
|
.filter(element => element !== siteStoreKey);
|
||||||
|
await sync.storageArea.set({ deletedSiteList: newDeletedSiteList });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangedListener(changes, areaName) {
|
||||||
|
if (areaName === "sync") sync.errorHandledRunSync();
|
||||||
|
},
|
||||||
|
|
||||||
|
async addToDeletedList(changeInfo) {
|
||||||
|
const identity = changeInfo.contextualIdentity;
|
||||||
|
const deletedUUID =
|
||||||
|
await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
||||||
|
await identityState.storageArea.remove(identity.cookieStoreId);
|
||||||
|
sync.storageArea.backup({uuid: deletedUUID});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
|
||||||
|
if (syncEnabled) {
|
||||||
|
// Add listener to sync storage and containers.
|
||||||
|
// Works for all installs that have any sync storage.
|
||||||
|
// Waits for sync storage change before kicking off the restore/backup
|
||||||
|
// initial sync must be kicked off by user.
|
||||||
|
this.checkForListenersMaybeAdd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.checkForListenersMaybeRemove();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async errorHandledRunSync () {
|
||||||
|
await sync.runSync().catch( async (error)=> {
|
||||||
|
if (SYNC_DEBUG) console.error("Error from runSync", error);
|
||||||
|
await sync.checkForListenersMaybeAdd();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkForListenersMaybeAdd() {
|
||||||
|
const hasStorageListener =
|
||||||
|
await browser.storage.onChanged.hasListener(
|
||||||
|
sync.storageArea.onChangedListener
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasCIListener = await sync.hasContextualIdentityListeners();
|
||||||
|
|
||||||
|
if (!hasCIListener) {
|
||||||
|
await sync.addContextualIdentityListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStorageListener) {
|
||||||
|
await browser.storage.onChanged.addListener(
|
||||||
|
sync.storageArea.onChangedListener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkForListenersMaybeRemove() {
|
||||||
|
const hasStorageListener =
|
||||||
|
await browser.storage.onChanged.hasListener(
|
||||||
|
sync.storageArea.onChangedListener
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasCIListener = await sync.hasContextualIdentityListeners();
|
||||||
|
|
||||||
|
if (hasCIListener) {
|
||||||
|
await sync.removeContextualIdentityListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStorageListener) {
|
||||||
|
await browser.storage.onChanged.removeListener(
|
||||||
|
sync.storageArea.onChangedListener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async runSync() {
|
||||||
|
if (SYNC_DEBUG) {
|
||||||
|
const syncInfo = await sync.storageArea.get();
|
||||||
|
const localInfo = await browser.storage.local.get();
|
||||||
|
const idents = await browser.contextualIdentities.query({});
|
||||||
|
console.log("Initial State:", {syncInfo, localInfo, idents});
|
||||||
|
}
|
||||||
|
await sync.checkForListenersMaybeRemove();
|
||||||
|
if (SYNC_DEBUG) console.log("runSync");
|
||||||
|
|
||||||
|
await identityState.storageArea.upgradeData();
|
||||||
|
await assignManager.storageArea.upgradeData();
|
||||||
|
|
||||||
|
const hasSyncStorage = await sync.storageArea.hasSyncStorage();
|
||||||
|
if (hasSyncStorage) await restore();
|
||||||
|
|
||||||
|
await sync.storageArea.backup();
|
||||||
|
await removeOldDeletedItems();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addContextualIdentityListeners() {
|
||||||
|
await browser.contextualIdentities.onCreated.addListener(sync.storageArea.backup);
|
||||||
|
await browser.contextualIdentities.onRemoved.addListener(sync.storageArea.addToDeletedList);
|
||||||
|
await browser.contextualIdentities.onUpdated.addListener(sync.storageArea.backup);
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeContextualIdentityListeners() {
|
||||||
|
await browser.contextualIdentities.onCreated.removeListener(sync.storageArea.backup);
|
||||||
|
await browser.contextualIdentities.onRemoved.removeListener(sync.storageArea.addToDeletedList);
|
||||||
|
await browser.contextualIdentities.onUpdated.removeListener(sync.storageArea.backup);
|
||||||
|
},
|
||||||
|
|
||||||
|
async hasContextualIdentityListeners() {
|
||||||
|
return (
|
||||||
|
await browser.contextualIdentities.onCreated.hasListener(sync.storageArea.backup) &&
|
||||||
|
await browser.contextualIdentities.onRemoved.hasListener(sync.storageArea.addToDeletedList) &&
|
||||||
|
await browser.contextualIdentities.onUpdated.hasListener(sync.storageArea.backup)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async resetSync() {
|
||||||
|
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
|
||||||
|
if (syncEnabled) {
|
||||||
|
this.errorHandledRunSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.checkForListenersMaybeRemove();
|
||||||
|
await this.storageArea.removeThisInstanceFromSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// attaching to window for use in mocha tests
|
||||||
|
window.sync = sync;
|
||||||
|
|
||||||
|
sync.init();
|
||||||
|
|
||||||
|
async function restore() {
|
||||||
|
if (SYNC_DEBUG) console.log("restore");
|
||||||
|
await reconcileIdentities();
|
||||||
|
await reconcileSiteAssignments();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks for the container name. If it exists, they are assumed to be the
|
||||||
|
* same container, and the color and icon are overwritten from sync, if
|
||||||
|
* different.
|
||||||
|
*/
|
||||||
|
async function reconcileIdentities(){
|
||||||
|
if (SYNC_DEBUG) console.log("reconcileIdentities");
|
||||||
|
|
||||||
|
// first delete any from the deleted list
|
||||||
|
const deletedIdentityList =
|
||||||
|
await sync.storageArea.getDeletedIdentityList();
|
||||||
|
// first remove any deleted identities
|
||||||
|
for (const deletedUUID of deletedIdentityList) {
|
||||||
|
const deletedCookieStoreId =
|
||||||
|
await identityState.lookupCookieStoreId(deletedUUID);
|
||||||
|
if (deletedCookieStoreId){
|
||||||
|
try{
|
||||||
|
await browser.contextualIdentities.remove(deletedCookieStoreId);
|
||||||
|
} catch (error) {
|
||||||
|
// if the identity we are deleting is not there, that's fine.
|
||||||
|
console.error("Error deleting contextualIdentity", deletedCookieStoreId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const localIdentities = await browser.contextualIdentities.query({});
|
||||||
|
const syncIdentitiesRemoveDupes =
|
||||||
|
await sync.storageArea.getIdentities();
|
||||||
|
// find any local dupes created on sync storage and delete from sync storage
|
||||||
|
for (const localIdentity of localIdentities) {
|
||||||
|
const syncIdentitiesOfName = syncIdentitiesRemoveDupes
|
||||||
|
.filter(identity => identity.name === localIdentity.name);
|
||||||
|
if (syncIdentitiesOfName.length > 1) {
|
||||||
|
const identityMatchingContextId = syncIdentitiesOfName
|
||||||
|
.find(identity => identity.cookieStoreId === localIdentity.cookieStoreId);
|
||||||
|
if (identityMatchingContextId)
|
||||||
|
await sync.storageArea.removeIdentityKeyFromSync(identityMatchingContextId.macAddonUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const syncIdentities =
|
||||||
|
await sync.storageArea.getIdentities();
|
||||||
|
// now compare all containers for matching names.
|
||||||
|
for (const syncIdentity of syncIdentities) {
|
||||||
|
if (syncIdentity.macAddonUUID){
|
||||||
|
const localMatch = localIdentities.find(
|
||||||
|
localIdentity => localIdentity.name === syncIdentity.name
|
||||||
|
);
|
||||||
|
if (!localMatch) {
|
||||||
|
// if there's no name match found, check on uuid,
|
||||||
|
const localCookieStoreID =
|
||||||
|
await identityState.lookupCookieStoreId(syncIdentity.macAddonUUID);
|
||||||
|
if (localCookieStoreID) {
|
||||||
|
await ifUUIDMatch(syncIdentity, localCookieStoreID);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await ifNoMatch(syncIdentity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names match, so use the info from Sync
|
||||||
|
await updateIdentityWithSyncInfo(syncIdentity, localMatch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// if no macAddonUUID, there is a problem with the sync info and it needs to be ignored.
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateSiteAssignmentUUIDs();
|
||||||
|
|
||||||
|
async function updateSiteAssignmentUUIDs(){
|
||||||
|
const sites = assignManager.storageArea.getAssignedSites();
|
||||||
|
for (const siteKey of Object.keys(sites)) {
|
||||||
|
await assignManager.storageArea.set(siteKey, sites[siteKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateIdentityWithSyncInfo(syncIdentity, localMatch) {
|
||||||
|
// Sync is truth. if there is a match, compare data and update as needed
|
||||||
|
if (syncIdentity.color !== localMatch.color
|
||||||
|
|| syncIdentity.icon !== localMatch.icon) {
|
||||||
|
await browser.contextualIdentities.update(
|
||||||
|
localMatch.cookieStoreId, {
|
||||||
|
name: syncIdentity.name,
|
||||||
|
color: syncIdentity.color,
|
||||||
|
icon: syncIdentity.icon
|
||||||
|
});
|
||||||
|
|
||||||
|
if (SYNC_DEBUG) {
|
||||||
|
if (localMatch.color !== syncIdentity.color) {
|
||||||
|
console.log(localMatch.name, "Change color: ", syncIdentity.color);
|
||||||
|
}
|
||||||
|
if (localMatch.icon !== syncIdentity.icon) {
|
||||||
|
console.log(localMatch.name, "Change icon: ", syncIdentity.icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sync is truth. If all is the same, update the local uuid to match sync
|
||||||
|
if (localMatch.macAddonUUID !== syncIdentity.macAddonUUID) {
|
||||||
|
await identityState.updateUUID(
|
||||||
|
localMatch.cookieStoreId,
|
||||||
|
syncIdentity.macAddonUUID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// TODOkmw: update any site assignment UUIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ifUUIDMatch(syncIdentity, localCookieStoreID) {
|
||||||
|
// if there's an identical local uuid, it's the same container. Sync is truth
|
||||||
|
const identityInfo = {
|
||||||
|
name: syncIdentity.name,
|
||||||
|
color: syncIdentity.color,
|
||||||
|
icon: syncIdentity.icon
|
||||||
|
};
|
||||||
|
if (SYNC_DEBUG) {
|
||||||
|
try {
|
||||||
|
const getIdent =
|
||||||
|
await browser.contextualIdentities.get(localCookieStoreID);
|
||||||
|
if (getIdent.name !== identityInfo.name) {
|
||||||
|
console.log(getIdent.name, "Change name: ", identityInfo.name);
|
||||||
|
}
|
||||||
|
if (getIdent.color !== identityInfo.color) {
|
||||||
|
console.log(getIdent.name, "Change color: ", identityInfo.color);
|
||||||
|
}
|
||||||
|
if (getIdent.icon !== identityInfo.icon) {
|
||||||
|
console.log(getIdent.name, "Change icon: ", identityInfo.icon);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//if this fails, there is probably differing sync info.
|
||||||
|
console.error("Error getting info on CI", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// update the local container with the sync data
|
||||||
|
await browser.contextualIdentities
|
||||||
|
.update(localCookieStoreID, identityInfo);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
// If this fails, sync info is off.
|
||||||
|
console.error("Error udpating CI", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ifNoMatch(syncIdentity){
|
||||||
|
// if no uuid match either, make new identity
|
||||||
|
if (SYNC_DEBUG) console.log("create new ident: ", syncIdentity.name);
|
||||||
|
const newIdentity =
|
||||||
|
await browser.contextualIdentities.create({
|
||||||
|
name: syncIdentity.name,
|
||||||
|
color: syncIdentity.color,
|
||||||
|
icon: syncIdentity.icon
|
||||||
|
});
|
||||||
|
await identityState.updateUUID(
|
||||||
|
newIdentity.cookieStoreId,
|
||||||
|
syncIdentity.macAddonUUID
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Checks for site previously assigned. If it exists, and has the same
|
||||||
|
* container assignment, the assignment is kept. If it exists, but has
|
||||||
|
* a different assignment, the user is prompted (not yet implemented).
|
||||||
|
* If it does not exist, it is created.
|
||||||
|
*/
|
||||||
|
async function reconcileSiteAssignments() {
|
||||||
|
if (SYNC_DEBUG) console.log("reconcileSiteAssignments");
|
||||||
|
const assignedSitesLocal =
|
||||||
|
await assignManager.storageArea.getAssignedSites();
|
||||||
|
const assignedSitesFromSync =
|
||||||
|
await sync.storageArea.getAssignedSites();
|
||||||
|
const deletedSiteList =
|
||||||
|
await sync.storageArea.getDeletedSiteList();
|
||||||
|
for(const siteStoreKey of deletedSiteList) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(assignedSitesLocal,siteStoreKey)) {
|
||||||
|
assignManager
|
||||||
|
.storageArea
|
||||||
|
.remove(siteStoreKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const urlKey of Object.keys(assignedSitesFromSync)) {
|
||||||
|
const assignedSite = assignedSitesFromSync[urlKey];
|
||||||
|
try{
|
||||||
|
if (assignedSite.identityMacAddonUUID) {
|
||||||
|
// Sync is truth.
|
||||||
|
// Not even looking it up. Just overwrite
|
||||||
|
if (SYNC_DEBUG){
|
||||||
|
const isInStorage = await assignManager.storageArea.getByUrlKey(urlKey);
|
||||||
|
if (!isInStorage)
|
||||||
|
console.log("new assignment ", assignedSite);
|
||||||
|
}
|
||||||
|
|
||||||
|
await setAssignmentWithUUID(assignedSite, urlKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// this is probably old or incorrect site info in Sync
|
||||||
|
// skip and move on.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MILISECONDS_IN_THIRTY_DAYS = 2592000000;
|
||||||
|
|
||||||
|
async function removeOldDeletedItems() {
|
||||||
|
const instanceList = await sync.storageArea.getAllInstanceInfo();
|
||||||
|
const deletedSiteList = await sync.storageArea.getDeletedSiteList();
|
||||||
|
const deletedIdentityList = await sync.storageArea.getDeletedIdentityList();
|
||||||
|
|
||||||
|
for (const instanceKey of Object.keys(instanceList)) {
|
||||||
|
const date = new Date();
|
||||||
|
const currentTimestamp = date.getTime();
|
||||||
|
if (instanceList[instanceKey].timestamp < currentTimestamp - MILISECONDS_IN_THIRTY_DAYS) {
|
||||||
|
delete instanceList[instanceKey];
|
||||||
|
sync.storageArea.removeInstance(instanceKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const siteStoreKey of deletedSiteList) {
|
||||||
|
let hasMatch = false;
|
||||||
|
for (const instance of Object.values(instanceList)) {
|
||||||
|
const match = instance.siteAssignments.find(element => element === siteStoreKey);
|
||||||
|
if (!match) continue;
|
||||||
|
hasMatch = true;
|
||||||
|
}
|
||||||
|
if (!hasMatch) {
|
||||||
|
await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const identityUUID of deletedIdentityList) {
|
||||||
|
let hasMatch = false;
|
||||||
|
for (const instance of Object.values(instanceList)) {
|
||||||
|
const match = instance.identities.find(element => element === identityUUID);
|
||||||
|
if (!match) continue;
|
||||||
|
hasMatch = true;
|
||||||
|
}
|
||||||
|
if (!hasMatch) {
|
||||||
|
await sync.storageArea.backup({undeleteUUID: identityUUID});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setAssignmentWithUUID(assignedSite, urlKey) {
|
||||||
|
const uuid = assignedSite.identityMacAddonUUID;
|
||||||
|
const cookieStoreId = await identityState.lookupCookieStoreId(uuid);
|
||||||
|
if (cookieStoreId) {
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
|
assignedSite.userContextId = cookieStoreId
|
||||||
|
.replace(/^firefox-container-/, "");
|
||||||
|
await assignManager.storageArea.set(
|
||||||
|
urlKey,
|
||||||
|
assignedSite,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error (`No cookieStoreId found for: ${uuid}, ${urlKey}`);
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
async function requestPermissions() {
|
async function requestPermissions() {
|
||||||
const checkbox = document.querySelector("#bookmarksPermissions");
|
const checkbox = document.querySelector("#bookmarksPermissions");
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
|
@ -13,12 +12,17 @@ async function requestPermissions() {
|
||||||
browser.runtime.sendMessage({ method: "resetBookmarksContext" });
|
browser.runtime.sendMessage({ method: "resetBookmarksContext" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restoreOptions() {
|
async function enableDisableSync() {
|
||||||
const hasPermission = await browser.permissions.contains({ permissions: ["bookmarks"] });
|
const checkbox = document.querySelector("#syncCheck");
|
||||||
if (hasPermission) {
|
if (checkbox.checked) {
|
||||||
document.querySelector("#bookmarksPermissions").checked = true;
|
await browser.storage.local.set({syncEnabled: true});
|
||||||
|
} else {
|
||||||
|
await browser.storage.local.set({syncEnabled: false});
|
||||||
}
|
}
|
||||||
|
browser.runtime.sendMessage({ method: "resetSync" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreOptions() {
|
||||||
const backupLink = document.getElementById("containers-save-link");
|
const backupLink = document.getElementById("containers-save-link");
|
||||||
document.getElementById("containers-save-button").addEventListener("click", async () => {
|
document.getElementById("containers-save-button").addEventListener("click", async () => {
|
||||||
const content = JSON.stringify(
|
const content = JSON.stringify(
|
||||||
|
@ -46,8 +50,20 @@ async function restoreOptions() {
|
||||||
}
|
}
|
||||||
restoreInput.value = "";
|
restoreInput.value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]});
|
||||||
|
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||||
|
if (hasPermission) {
|
||||||
|
document.querySelector("#bookmarksPermissions").checked = true;
|
||||||
|
}
|
||||||
|
if (syncEnabled) {
|
||||||
|
document.querySelector("#syncCheck").checked = true;
|
||||||
|
} else {
|
||||||
|
document.querySelector("#syncCheck").checked = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||||
document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions);
|
document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions);
|
||||||
|
document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync);
|
||||||
|
|
|
@ -17,6 +17,8 @@ const P_ONBOARDING_2 = "onboarding2";
|
||||||
const P_ONBOARDING_3 = "onboarding3";
|
const P_ONBOARDING_3 = "onboarding3";
|
||||||
const P_ONBOARDING_4 = "onboarding4";
|
const P_ONBOARDING_4 = "onboarding4";
|
||||||
const P_ONBOARDING_5 = "onboarding5";
|
const P_ONBOARDING_5 = "onboarding5";
|
||||||
|
const P_ONBOARDING_6 = "onboarding6";
|
||||||
|
const P_ONBOARDING_7 = "onboarding7";
|
||||||
const P_CONTAINERS_LIST = "containersList";
|
const P_CONTAINERS_LIST = "containersList";
|
||||||
const P_CONTAINERS_EDIT = "containersEdit";
|
const P_CONTAINERS_EDIT = "containersEdit";
|
||||||
const P_CONTAINER_INFO = "containerInfo";
|
const P_CONTAINER_INFO = "containerInfo";
|
||||||
|
@ -99,9 +101,15 @@ const Logic = {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (onboarded) {
|
switch (onboarded) {
|
||||||
case 5:
|
case 7:
|
||||||
this.showAchievementOrContainersListPanel();
|
this.showAchievementOrContainersListPanel();
|
||||||
break;
|
break;
|
||||||
|
case 6:
|
||||||
|
this.showPanel(P_ONBOARDING_7);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.showPanel(P_ONBOARDING_6);
|
||||||
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
this.showPanel(P_ONBOARDING_5);
|
this.showPanel(P_ONBOARDING_5);
|
||||||
break;
|
break;
|
||||||
|
@ -352,6 +360,9 @@ const Logic = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getAssignmentObjectByContainer(userContextId) {
|
getAssignmentObjectByContainer(userContextId) {
|
||||||
|
if (!userContextId) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return browser.runtime.sendMessage({
|
return browser.runtime.sendMessage({
|
||||||
method: "getAssignmentObjectByContainer",
|
method: "getAssignmentObjectByContainer",
|
||||||
message: { userContextId }
|
message: { userContextId }
|
||||||
|
@ -500,6 +511,39 @@ Logic.registerPanel(P_ONBOARDING_5, {
|
||||||
// Let's move to the containers list panel.
|
// Let's move to the containers list panel.
|
||||||
Logic.addEnterHandler(document.querySelector("#onboarding-longpress-button"), async () => {
|
Logic.addEnterHandler(document.querySelector("#onboarding-longpress-button"), async () => {
|
||||||
await Logic.setOnboardingStage(5);
|
await Logic.setOnboardingStage(5);
|
||||||
|
Logic.showPanel(P_ONBOARDING_6);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// This method is called when the panel is shown.
|
||||||
|
prepare() {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// P_ONBOARDING_6: Sixth page for Onboarding: new tab long-press behavior
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Logic.registerPanel(P_ONBOARDING_6, {
|
||||||
|
panelSelector: ".onboarding-panel-6",
|
||||||
|
|
||||||
|
// This method is called when the object is registered.
|
||||||
|
initialize() {
|
||||||
|
// Let's move to the containers list panel.
|
||||||
|
Logic.addEnterHandler(document.querySelector("#start-sync-button"), async () => {
|
||||||
|
await Logic.setOnboardingStage(6);
|
||||||
|
await browser.storage.local.set({syncEnabled: true});
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
method: "resetSync"
|
||||||
|
});
|
||||||
|
Logic.showPanel(P_ONBOARDING_7);
|
||||||
|
});
|
||||||
|
Logic.addEnterHandler(document.querySelector("#no-sync"), async () => {
|
||||||
|
await Logic.setOnboardingStage(7);
|
||||||
|
await browser.storage.local.set({syncEnabled: false});
|
||||||
|
await browser.runtime.sendMessage({
|
||||||
|
method: "resetSync"
|
||||||
|
});
|
||||||
Logic.showPanel(P_CONTAINERS_LIST);
|
Logic.showPanel(P_CONTAINERS_LIST);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -510,6 +554,33 @@ Logic.registerPanel(P_ONBOARDING_5, {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// P_ONBOARDING_6: Sixth page for Onboarding: new tab long-press behavior
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Logic.registerPanel(P_ONBOARDING_7, {
|
||||||
|
panelSelector: ".onboarding-panel-7",
|
||||||
|
|
||||||
|
// This method is called when the object is registered.
|
||||||
|
initialize() {
|
||||||
|
// Let's move to the containers list panel.
|
||||||
|
Logic.addEnterHandler(document.querySelector("#sign-in"), async () => {
|
||||||
|
browser.tabs.create({
|
||||||
|
url: "https://accounts.firefox.com/?service=sync&action=email&context=fx_desktop_v3&entrypoint=multi-account-containers&utm_source=addon&utm_medium=panel&utm_campaign=container-sync",
|
||||||
|
});
|
||||||
|
await Logic.setOnboardingStage(7);
|
||||||
|
Logic.showPanel(P_CONTAINERS_LIST);
|
||||||
|
});
|
||||||
|
Logic.addEnterHandler(document.querySelector("#no-sign-in"), async () => {
|
||||||
|
await Logic.setOnboardingStage(7);
|
||||||
|
Logic.showPanel(P_CONTAINERS_LIST);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// This method is called when the panel is shown.
|
||||||
|
prepare() {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
// P_CONTAINERS_LIST: The list of containers. The main page.
|
// P_CONTAINERS_LIST: The list of containers. The main page.
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -721,8 +792,12 @@ Logic.registerPanel(P_CONTAINERS_LIST, {
|
||||||
however it allows us to have a tabindex before the first selected item
|
however it allows us to have a tabindex before the first selected item
|
||||||
*/
|
*/
|
||||||
const focusHandler = () => {
|
const focusHandler = () => {
|
||||||
list.querySelector("tr .clickable").focus();
|
const identityList = list.querySelector("tr .clickable");
|
||||||
document.removeEventListener("focus", focusHandler);
|
if (identityList) {
|
||||||
|
// otherwise this throws an error when there are no containers present.
|
||||||
|
identityList.focus();
|
||||||
|
document.removeEventListener("focus", focusHandler);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener("focus", focusHandler);
|
document.addEventListener("focus", focusHandler);
|
||||||
/* If the user mousedown's first then remove the focus handler */
|
/* If the user mousedown's first then remove the focus handler */
|
||||||
|
@ -1022,6 +1097,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
|
||||||
while (tableElement.firstChild) {
|
while (tableElement.firstChild) {
|
||||||
tableElement.firstChild.remove();
|
tableElement.firstChild.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
assignmentKeys.forEach((siteKey) => {
|
assignmentKeys.forEach((siteKey) => {
|
||||||
const site = assignments[siteKey];
|
const site = assignments[siteKey];
|
||||||
const trElement = document.createElement("div");
|
const trElement = document.createElement("div");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Firefox Multi-Account Containers",
|
"name": "Firefox Multi-Account Containers",
|
||||||
"version": "6.1.1",
|
"version": "6.2.0",
|
||||||
"incognito": "not_allowed",
|
"incognito": "not_allowed",
|
||||||
"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.",
|
"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": {
|
"icons": {
|
||||||
|
|
|
@ -10,6 +10,11 @@
|
||||||
Enable Bookmark Menus
|
Enable Bookmark Menus
|
||||||
</label>
|
</label>
|
||||||
<p>This setting allows you to open a bookmark or folder of bookmarks in a container.</p>
|
<p>This setting allows you to open a bookmark or folder of bookmarks in a container.</p>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="syncCheck">
|
||||||
|
Enable Sync
|
||||||
|
</label>
|
||||||
|
<p>This setting allows you to sync your containers and site assignments across devices.</p>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Restore</legend>
|
<legend>Restore</legend>
|
||||||
|
|
|
@ -64,7 +64,27 @@
|
||||||
<img class="onboarding-img" alt="Long-press the New Tab button to create a new container tab." src="/img/onboarding-3.png" />
|
<img class="onboarding-img" alt="Long-press the New Tab button to create a new container tab." src="/img/onboarding-3.png" />
|
||||||
<h3 class="onboarding-title">Container tabs when you need them.</h3>
|
<h3 class="onboarding-title">Container tabs when you need them.</h3>
|
||||||
<p>Long-press the New Tab button to create a new container tab.</p>
|
<p>Long-press the New Tab button to create a new container tab.</p>
|
||||||
<a href="#" id="onboarding-longpress-button" class="onboarding-button">Done</a>
|
<a href="#" id="onboarding-longpress-button" class="onboarding-button">Next</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel onboarding onboarding-panel-6 hide" id="onboarding-panel-6">
|
||||||
|
<img class="onboarding-img" alt="Syncing Containers is now Available!" src="/img/Sync.svg" />
|
||||||
|
<h3 class="onboarding-title">Syncing Containers is now Available!</h3>
|
||||||
|
<p>Turn on Sync to share container and site assignments with any computer connected to your Firefox Account.</p>
|
||||||
|
<div class="half-button-wrapper">
|
||||||
|
<a herf="#" id="no-sync" class="half-onboarding-button grey-button">Not Now</a>
|
||||||
|
<a href="#" id="start-sync-button" class="half-onboarding-button">Start Syncing</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel onboarding onboarding-panel-7 hide" id="onboarding-panel-7">
|
||||||
|
<img class="onboarding-img" alt="Firefox Account is required to sync" src="/img/Account.svg" />
|
||||||
|
<h3 class="onboarding-title">Firefox Account is required to sync.</h3>
|
||||||
|
<p>Click Sign In to confirm that your Firefox Account is active.</p>
|
||||||
|
<div class="half-button-wrapper">
|
||||||
|
<a herf="#" id="no-sign-in" class="half-onboarding-button grey-button">Not Now</a>
|
||||||
|
<a href="#" id="sign-in" class="half-onboarding-button">Sign In</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel achievement-panel hide" id="achievement-panel">
|
<div class="panel achievement-panel hide" id="achievement-panel">
|
||||||
|
|
|
@ -6,15 +6,7 @@ module.exports = {
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2018
|
"ecmaVersion": 2018
|
||||||
},
|
},
|
||||||
globals: {
|
"rules": {
|
||||||
"sinon": false,
|
"no-restricted-globals": ["error", "browser"]
|
||||||
"expect": false,
|
|
||||||
"nextTick": false,
|
|
||||||
"buildDom": false,
|
|
||||||
"buildBackgroundDom": false,
|
|
||||||
"background": false,
|
|
||||||
"buildPopupDom": false,
|
|
||||||
"popup": false,
|
|
||||||
"helper": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
test/common.js
Normal file
111
test/common.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
if (!process.listenerCount("unhandledRejection")) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
process.on("unhandledRejection", r => console.log(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const chai = require("chai");
|
||||||
|
const sinonChai = require("sinon-chai");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const sinon = require("sinon");
|
||||||
|
const expect = chai.expect;
|
||||||
|
chai.should();
|
||||||
|
chai.use(sinonChai);
|
||||||
|
const nextTick = () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
process.nextTick(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const webExtensionsJSDOM = require("webextensions-jsdom");
|
||||||
|
const manifestPath = path.resolve(path.join(__dirname, "../src/manifest.json"));
|
||||||
|
|
||||||
|
const buildDom = async ({background = {}, popup = {}}) => {
|
||||||
|
background = {
|
||||||
|
...background,
|
||||||
|
jsdom: {
|
||||||
|
...background.jsom,
|
||||||
|
beforeParse(window) {
|
||||||
|
window.browser.permissions.getAll.resolves({permissions: ["bookmarks"]});
|
||||||
|
window.crypto = {
|
||||||
|
getRandomValues: arr => crypto.randomBytes(arr.length),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
popup = {
|
||||||
|
...popup,
|
||||||
|
jsdom: {
|
||||||
|
...popup.jsdom,
|
||||||
|
pretendToBeVisual: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, {
|
||||||
|
apiFake: true,
|
||||||
|
wiring: true,
|
||||||
|
sinon: global.sinon,
|
||||||
|
background,
|
||||||
|
popup
|
||||||
|
});
|
||||||
|
|
||||||
|
webExtension.browser = webExtension.background.browser;
|
||||||
|
return webExtension;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildBackgroundDom = background => {
|
||||||
|
return buildDom({
|
||||||
|
background,
|
||||||
|
popup: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildPopupDom = popup => {
|
||||||
|
return buildDom({
|
||||||
|
popup,
|
||||||
|
background: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeWithTab = async (details = {
|
||||||
|
cookieStoreId: "firefox-default"
|
||||||
|
}) => {
|
||||||
|
let tab;
|
||||||
|
const webExtension = await buildDom({
|
||||||
|
background: {
|
||||||
|
async afterBuild(background) {
|
||||||
|
tab = await background.browser.tabs._create(details);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
popup: {
|
||||||
|
jsdom: {
|
||||||
|
beforeParse(window) {
|
||||||
|
window.browser.storage.local.set({
|
||||||
|
"browserActionBadgesClicked": [],
|
||||||
|
"onboarding-stage": 7,
|
||||||
|
"achievements": [],
|
||||||
|
"syncEnabled": true
|
||||||
|
});
|
||||||
|
window.browser.storage.local.set.resetHistory();
|
||||||
|
window.browser.storage.sync.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
webExtension.tab = tab;
|
||||||
|
|
||||||
|
return webExtension;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildDom,
|
||||||
|
buildBackgroundDom,
|
||||||
|
buildPopupDom,
|
||||||
|
initializeWithTab,
|
||||||
|
sinon,
|
||||||
|
expect,
|
||||||
|
nextTick,
|
||||||
|
};
|
|
@ -1,25 +1,30 @@
|
||||||
describe("Assignment Feature", () => {
|
const {initializeWithTab} = require("../common");
|
||||||
|
|
||||||
|
describe("Assignment Feature", function () {
|
||||||
const url = "http://example.com";
|
const url = "http://example.com";
|
||||||
|
|
||||||
let activeTab;
|
beforeEach(async function () {
|
||||||
beforeEach(async () => {
|
this.webExt = await initializeWithTab({
|
||||||
activeTab = await helper.browser.initializeWithTab({
|
|
||||||
cookieStoreId: "firefox-container-1",
|
cookieStoreId: "firefox-container-1",
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("click the 'Always open in' checkbox in the popup", () => {
|
afterEach(function () {
|
||||||
beforeEach(async () => {
|
this.webExt.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("click the 'Always open in' checkbox in the popup", function () {
|
||||||
|
beforeEach(async function () {
|
||||||
// popup click to set assignment for activeTab.url
|
// popup click to set assignment for activeTab.url
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
await this.webExt.popup.helper.clickElementById("container-page-assigned");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("open new Tab with the assigned URL in the default container", () => {
|
describe("open new Tab with the assigned URL in the default container", function () {
|
||||||
let newTab;
|
let newTab;
|
||||||
beforeEach(async () => {
|
beforeEach(async function () {
|
||||||
// new Tab opening activeTab.url in default container
|
// new Tab opening activeTab.url in default container
|
||||||
newTab = await helper.browser.openNewTab({
|
newTab = await this.webExt.background.browser.tabs._create({
|
||||||
cookieStoreId: "firefox-default",
|
cookieStoreId: "firefox-default",
|
||||||
url
|
url
|
||||||
}, {
|
}, {
|
||||||
|
@ -29,12 +34,12 @@ describe("Assignment Feature", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should open the confirm page", async () => {
|
it("should open the confirm page", async function () {
|
||||||
// should have created a new tab with the confirm page
|
// should have created a new tab with the confirm page
|
||||||
background.browser.tabs.create.should.have.been.calledWithMatch({
|
this.webExt.background.browser.tabs.create.should.have.been.calledWithMatch({
|
||||||
url: "moz-extension://fake/confirm-page.html?" +
|
url: "moz-extension://fake/confirm-page.html?" +
|
||||||
`url=${encodeURIComponent(url)}` +
|
`url=${encodeURIComponent(url)}` +
|
||||||
`&cookieStoreId=${activeTab.cookieStoreId}`,
|
`&cookieStoreId=${this.webExt.tab.cookieStoreId}`,
|
||||||
cookieStoreId: undefined,
|
cookieStoreId: undefined,
|
||||||
openerTabId: null,
|
openerTabId: null,
|
||||||
index: 2,
|
index: 2,
|
||||||
|
@ -42,29 +47,29 @@ describe("Assignment Feature", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove the new Tab that got opened in the default container", () => {
|
it("should remove the new Tab that got opened in the default container", function () {
|
||||||
background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
|
this.webExt.background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("click the 'Always open in' checkbox in the popup again", () => {
|
describe("click the 'Always open in' checkbox in the popup again", function () {
|
||||||
beforeEach(async () => {
|
beforeEach(async function () {
|
||||||
// popup click to remove assignment for activeTab.url
|
// popup click to remove assignment for activeTab.url
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
await this.webExt.popup.helper.clickElementById("container-page-assigned");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("open new Tab with the no longer assigned URL in the default container", () => {
|
describe("open new Tab with the no longer assigned URL in the default container", function () {
|
||||||
beforeEach(async () => {
|
beforeEach(async function () {
|
||||||
// new Tab opening activeTab.url in default container
|
// new Tab opening activeTab.url in default container
|
||||||
await helper.browser.openNewTab({
|
await this.webExt.background.browser.tabs._create({
|
||||||
cookieStoreId: "firefox-default",
|
cookieStoreId: "firefox-default",
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not open the confirm page", async () => {
|
it("should not open the confirm page", async function () {
|
||||||
// should not have created a new tab
|
// should not have created a new tab
|
||||||
background.browser.tabs.create.should.not.have.been.called;
|
this.webExt.background.browser.tabs.create.should.not.have.been.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
describe("Containers Management", () => {
|
const {initializeWithTab} = require("../common");
|
||||||
beforeEach(async () => {
|
|
||||||
await helper.browser.initializeWithTab();
|
describe("Containers Management", function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.webExt = await initializeWithTab();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("creating a new container", () => {
|
afterEach(function () {
|
||||||
beforeEach(async () => {
|
this.webExt.destroy();
|
||||||
await helper.popup.clickElementById("container-add-link");
|
});
|
||||||
await helper.popup.clickElementById("edit-container-ok-link");
|
|
||||||
|
describe("creating a new container", function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
await this.webExt.popup.helper.clickElementById("container-add-link");
|
||||||
|
await this.webExt.popup.helper.clickElementById("edit-container-ok-link");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create it in the browser as well", () => {
|
it("should create it in the browser as well", function () {
|
||||||
background.browser.contextualIdentities.create.should.have.been.calledOnce;
|
this.webExt.background.browser.contextualIdentities.create.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("removing it afterwards", () => {
|
describe("removing it afterwards", function () {
|
||||||
beforeEach(async () => {
|
beforeEach(async function () {
|
||||||
await helper.popup.clickElementById("edit-containers-link");
|
await this.webExt.popup.helper.clickElementById("edit-containers-link");
|
||||||
await helper.popup.clickLastMatchingElementByQuerySelector(".delete-container-icon");
|
await this.webExt.popup.helper.clickElementByQuerySelectorAll(".delete-container-icon", "last");
|
||||||
await helper.popup.clickElementById("delete-container-ok-link");
|
await this.webExt.popup.helper.clickElementById("delete-container-ok-link");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove it in the browser as well", () => {
|
it("should remove it in the browser as well", function () {
|
||||||
background.browser.contextualIdentities.remove.should.have.been.calledOnce;
|
this.webExt.background.browser.contextualIdentities.remove.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
describe("External Webextensions", () => {
|
const {expect, initializeWithTab} = require("../common");
|
||||||
|
|
||||||
|
describe("External Webextensions", function () {
|
||||||
const url = "http://example.com";
|
const url = "http://example.com";
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async function () {
|
||||||
await helper.browser.initializeWithTab({
|
this.webExt = await initializeWithTab({
|
||||||
cookieStoreId: "firefox-container-1",
|
cookieStoreId: "firefox-container-1",
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
|
||||||
|
await this.webExt.popup.helper.clickElementById("container-page-assigned");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with contextualIdentities permissions", () => {
|
afterEach(function () {
|
||||||
it("should be able to get assignments", async () => {
|
this.webExt.destroy();
|
||||||
background.browser.management.get.resolves({
|
});
|
||||||
|
|
||||||
|
describe("with contextualIdentities permissions", function () {
|
||||||
|
it("should be able to get assignments", async function () {
|
||||||
|
this.webExt.background.browser.management.get.resolves({
|
||||||
permissions: ["contextualIdentities"]
|
permissions: ["contextualIdentities"]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,18 +30,19 @@ describe("External Webextensions", () => {
|
||||||
id: "external-webextension"
|
id: "external-webextension"
|
||||||
};
|
};
|
||||||
|
|
||||||
const [promise] = background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
|
const [promise] = this.webExt.background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
|
||||||
const answer = await promise;
|
const answer = await promise;
|
||||||
expect(answer).to.deep.equal({
|
expect(answer.userContextId === "1").to.be.true;
|
||||||
userContextId: "1",
|
expect(answer.neverAsk === false).to.be.true;
|
||||||
neverAsk: false
|
expect(
|
||||||
});
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
answer, "identityMacAddonUUID")).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("without contextualIdentities permissions", () => {
|
describe("without contextualIdentities permissions", function () {
|
||||||
it("should throw an error", async () => {
|
it("should throw an error", async function () {
|
||||||
background.browser.management.get.resolves({
|
this.webExt.background.browser.management.get.resolves({
|
||||||
permissions: []
|
permissions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +54,7 @@ describe("External Webextensions", () => {
|
||||||
id: "external-webextension"
|
id: "external-webextension"
|
||||||
};
|
};
|
||||||
|
|
||||||
const [promise] = background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
|
const [promise] = this.webExt.background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
|
||||||
return promise.catch(error => {
|
return promise.catch(error => {
|
||||||
expect(error.message).to.equal("Missing contextualIdentities permission");
|
expect(error.message).to.equal("Missing contextualIdentities permission");
|
||||||
});
|
});
|
||||||
|
|
465
test/features/sync.test.js
Normal file
465
test/features/sync.test.js
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
const {initializeWithTab} = require("../common");
|
||||||
|
|
||||||
|
describe("Sync", function() {
|
||||||
|
beforeEach(async function() {
|
||||||
|
this.webExt = await initializeWithTab();
|
||||||
|
this.syncHelper = new SyncTestHelper(this.webExt);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
this.webExt.destroy();
|
||||||
|
delete this.syncHelper;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("testIdentityStateCleanup", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
|
||||||
|
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []);
|
||||||
|
|
||||||
|
await this.webExt.browser.storage.local.set({
|
||||||
|
"identitiesState@@_firefox-container-5": {
|
||||||
|
"hiddenTabs": []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.webExt.background.window.identityState.storageArea.upgradeData();
|
||||||
|
|
||||||
|
const macConfigs = await this.webExt.browser.storage.local.get();
|
||||||
|
const identities = [];
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey.includes("identitiesState@@_") && !configKey.includes("default")) {
|
||||||
|
identities.push(macConfigs[configKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
identities.should.have.lengthOf(5, "There should be 5 identity entries");
|
||||||
|
for (const identity of identities) {
|
||||||
|
(!!identity.macAddonUUID).should.be.true;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("testAssignManagerCleanup", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
|
||||||
|
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, TEST_ASSIGNMENTS);
|
||||||
|
|
||||||
|
await this.webExt.browser.storage.local.set({
|
||||||
|
"siteContainerMap@@_www.goop.com": {
|
||||||
|
"userContextId": "999",
|
||||||
|
"neverAsk": true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.webExt.background.window.identityState.storageArea.upgradeData();
|
||||||
|
await this.webExt.background.window.assignManager.storageArea.upgradeData();
|
||||||
|
const macConfigs = await this.webExt.browser.storage.local.get();
|
||||||
|
const assignments = [];
|
||||||
|
for(const configKey of Object.keys(macConfigs)) {
|
||||||
|
if (configKey.includes("siteContainerMap@@_")) {
|
||||||
|
macConfigs[configKey].configKey = configKey;
|
||||||
|
assignments.push(macConfigs[configKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignments.should.have.lengthOf(5, "There should be 5 site assignments");
|
||||||
|
for (const assignment of assignments) {
|
||||||
|
(!!assignment.identityMacAddonUUID).should.be.true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("testReconcileSiteAssignments", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
|
||||||
|
await this.syncHelper.setState(
|
||||||
|
DUPE_TEST_SYNC,
|
||||||
|
LOCAL_DATA,
|
||||||
|
TEST_CONTAINERS,
|
||||||
|
SITE_ASSIGNMENT_TEST
|
||||||
|
);
|
||||||
|
|
||||||
|
// add 200ok (bad data).
|
||||||
|
const testSites = {
|
||||||
|
"siteContainerMap@@_developer.mozilla.org": {
|
||||||
|
"userContextId": "588",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e",
|
||||||
|
"hostname": "developer.mozilla.org"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_reddit.com": {
|
||||||
|
"userContextId": "592",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7",
|
||||||
|
"hostname": "reddit.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_twitter.com": {
|
||||||
|
"userContextId": "589",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187",
|
||||||
|
"hostname": "twitter.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_www.facebook.com": {
|
||||||
|
"userContextId": "590",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4",
|
||||||
|
"hostname": "www.facebook.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_www.linkedin.com": {
|
||||||
|
"userContextId": "591",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1",
|
||||||
|
"hostname": "www.linkedin.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_200ok.us": {
|
||||||
|
"userContextId": "1",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "b5f5f794-b37e-4cec-9f4e-6490df620336",
|
||||||
|
"hostname": "www.linkedin.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const site of Object.keys(testSites)) {
|
||||||
|
await this.webExt.browser.storage.sync.set({[site]:testSites[site]});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.webExt.browser.storage.sync.set({
|
||||||
|
deletedSiteList: ["siteContainerMap@@_www.google.com"]
|
||||||
|
});
|
||||||
|
await this.webExt.background.window.sync.runSync();
|
||||||
|
|
||||||
|
const assignedSites = await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
|
||||||
|
Object.keys(assignedSites).should.have.lengthOf(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("testInitialSync", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []);
|
||||||
|
await this.webExt.background.window.sync.runSync();
|
||||||
|
|
||||||
|
const getAssignedSites =
|
||||||
|
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
|
||||||
|
const identities = await this.webExt.browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
identities.should.have.lengthOf(5, "There should be 5 identity entries");
|
||||||
|
Object.keys(getAssignedSites).should.have.lengthOf(0, "There should be no site assignments");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("test2", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
|
||||||
|
await this.syncHelper.setState(SYNC_DATA, LOCAL_DATA, TEST_CONTAINERS, TEST_ASSIGNMENTS);
|
||||||
|
|
||||||
|
await this.webExt.background.window.sync.runSync();
|
||||||
|
|
||||||
|
const getAssignedSites =
|
||||||
|
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
|
||||||
|
|
||||||
|
const identities = await this.webExt.browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
identities.should.have.lengthOf(6, "There should be 6 identity entries");
|
||||||
|
Object.keys(getAssignedSites).should.have.lengthOf(5, "There should be 5 site assignments");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dupeTest", async function() {
|
||||||
|
await this.syncHelper.stopSyncListeners();
|
||||||
|
await this.syncHelper.setState(
|
||||||
|
DUPE_TEST_SYNC,
|
||||||
|
DUPE_TEST_LOCAL,
|
||||||
|
DUPE_TEST_IDENTS,
|
||||||
|
DUPE_TEST_ASSIGNMENTS
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.webExt.background.window.sync.runSync();
|
||||||
|
|
||||||
|
const getAssignedSites =
|
||||||
|
await this.webExt.background.window.assignManager.storageArea.getAssignedSites();
|
||||||
|
|
||||||
|
const identities = await this.webExt.browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
identities.should.have.lengthOf(7, "There should be 7 identity entries");
|
||||||
|
|
||||||
|
Object.keys(getAssignedSites).should.have.lengthOf(5, "There should be 5 identity entries");
|
||||||
|
|
||||||
|
const personalContainer =
|
||||||
|
this.syncHelper.lookupIdentityBy(identities, {name: "Personal"});
|
||||||
|
(personalContainer.color === "red").should.be.true;
|
||||||
|
|
||||||
|
const mozillaContainer =
|
||||||
|
this.syncHelper.lookupIdentityBy(identities, {name: "Mozilla"});
|
||||||
|
(mozillaContainer.icon === "pet").should.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class SyncTestHelper {
|
||||||
|
constructor(webExt) {
|
||||||
|
this.webExt = webExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopSyncListeners() {
|
||||||
|
await this.webExt.browser.storage.onChanged.removeListener(this.webExt.background.window.sync.storageArea.onChangedListener);
|
||||||
|
await this.webExt.background.window.sync.removeContextualIdentityListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setState(syncData, localData, identityData, assignmentData){
|
||||||
|
await this.removeAllContainers();
|
||||||
|
await this.webExt.browser.storage.sync.clear();
|
||||||
|
await this.webExt.browser.storage.sync.set(syncData);
|
||||||
|
await this.webExt.browser.storage.local.clear();
|
||||||
|
await this.webExt.browser.storage.local.set(localData);
|
||||||
|
for (let i=0; i < identityData.length; i++) {
|
||||||
|
//build identities
|
||||||
|
const newIdentity =
|
||||||
|
await this.webExt.browser.contextualIdentities.create(identityData[i]);
|
||||||
|
// fill identies with site assignments
|
||||||
|
if (assignmentData && assignmentData[i]) {
|
||||||
|
const data = {
|
||||||
|
"userContextId":
|
||||||
|
String(
|
||||||
|
newIdentity.cookieStoreId.replace(/^firefox-container-/, "")
|
||||||
|
),
|
||||||
|
"neverAsk": true
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.webExt.browser.storage.local.set({[assignmentData[i]]: data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAllContainers() {
|
||||||
|
const identities = await this.webExt.browser.contextualIdentities.query({});
|
||||||
|
for (const identity of identities) {
|
||||||
|
await this.webExt.browser.contextualIdentities.remove(identity.cookieStoreId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupIdentityBy(identities, options) {
|
||||||
|
for (const identity of identities) {
|
||||||
|
if (options && options.name) {
|
||||||
|
if (identity.name === options.name) return identity;
|
||||||
|
}
|
||||||
|
if (options && options.color) {
|
||||||
|
if (identity.color === options.color) return identity;
|
||||||
|
}
|
||||||
|
if (options && options.color) {
|
||||||
|
if (identity.color === options.color) return identity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_CONTAINERS = [
|
||||||
|
{
|
||||||
|
name: "Personal",
|
||||||
|
color: "blue",
|
||||||
|
icon: "fingerprint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Banking",
|
||||||
|
color: "green",
|
||||||
|
icon: "dollar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mozilla",
|
||||||
|
color: "red",
|
||||||
|
icon: "briefcase"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Groceries, obviously",
|
||||||
|
color: "yellow",
|
||||||
|
icon: "cart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Facebook",
|
||||||
|
color: "toolbar",
|
||||||
|
icon: "fence"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const TEST_ASSIGNMENTS = [
|
||||||
|
"siteContainerMap@@_developer.mozilla.org",
|
||||||
|
"siteContainerMap@@_twitter.com",
|
||||||
|
"siteContainerMap@@_www.facebook.com",
|
||||||
|
"siteContainerMap@@_www.linkedin.com",
|
||||||
|
"siteContainerMap@@_reddit.com"
|
||||||
|
];
|
||||||
|
|
||||||
|
const LOCAL_DATA = {
|
||||||
|
"browserActionBadgesClicked": [ "6.2.0" ],
|
||||||
|
"containerTabsOpened": 7,
|
||||||
|
"identitiesState@@_firefox-default": { "hiddenTabs": [] },
|
||||||
|
"onboarding-stage": 5
|
||||||
|
};
|
||||||
|
|
||||||
|
const SYNC_DATA = {
|
||||||
|
"identity@@_22ded543-5173-44a5-a47a-8813535945ca": {
|
||||||
|
"name": "Personal",
|
||||||
|
"icon": "fingerprint",
|
||||||
|
"color": "red",
|
||||||
|
"cookieStoreId": "firefox-container-146",
|
||||||
|
"macAddonUUID": "22ded543-5173-44a5-a47a-8813535945ca"
|
||||||
|
},
|
||||||
|
"identity@@_63e5212f-0858-418e-b5a3-09c2dea61fcd": {
|
||||||
|
"name": "Oscar",
|
||||||
|
"icon": "dollar",
|
||||||
|
"color": "green",
|
||||||
|
"cookieStoreId": "firefox-container-147",
|
||||||
|
"macAddonUUID": "3e5212f-0858-418e-b5a3-09c2dea61fcd"
|
||||||
|
},
|
||||||
|
"identity@@_71335417-158e-4d74-a55b-e9e9081601ec": {
|
||||||
|
"name": "Mozilla",
|
||||||
|
"icon": "pet",
|
||||||
|
"color": "red",
|
||||||
|
"cookieStoreId": "firefox-container-148",
|
||||||
|
"macAddonUUID": "71335417-158e-4d74-a55b-e9e9081601ec"
|
||||||
|
},
|
||||||
|
"identity@@_59c4e5f7-fe3b-435a-ae60-1340db31a91b": {
|
||||||
|
"name": "Groceries, obviously",
|
||||||
|
"icon": "cart",
|
||||||
|
"color": "pink",
|
||||||
|
"cookieStoreId": "firefox-container-149",
|
||||||
|
"macAddonUUID": "59c4e5f7-fe3b-435a-ae60-1340db31a91b"
|
||||||
|
},
|
||||||
|
"identity@@_3dc916fb-8c0a-4538-9758-73ef819a45f7": {
|
||||||
|
"name": "Facebook",
|
||||||
|
"icon": "fence",
|
||||||
|
"color": "toolbar",
|
||||||
|
"cookieStoreId": "firefox-container-150",
|
||||||
|
"macAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DUPE_TEST_SYNC = {
|
||||||
|
"identity@@_d20d7af2-9866-468e-bb43-541efe8c2c2e": {
|
||||||
|
"name": "Personal",
|
||||||
|
"icon": "fingerprint",
|
||||||
|
"color": "red",
|
||||||
|
"cookieStoreId": "firefox-container-588",
|
||||||
|
"macAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e"
|
||||||
|
},
|
||||||
|
"identity@@_cdd73c20-c26a-4c06-9b17-735c1f5e9187": {
|
||||||
|
"name": "Big Bird",
|
||||||
|
"icon": "pet",
|
||||||
|
"color": "yellow",
|
||||||
|
"cookieStoreId": "firefox-container-589",
|
||||||
|
"macAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187"
|
||||||
|
},
|
||||||
|
"identity@@_32cc4a9b-05ed-4e54-8e11-732468de62f4": {
|
||||||
|
"name": "Mozilla",
|
||||||
|
"icon": "pet",
|
||||||
|
"color": "red",
|
||||||
|
"cookieStoreId": "firefox-container-590",
|
||||||
|
"macAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4"
|
||||||
|
},
|
||||||
|
"identity@@_9ff381e3-4c11-420d-8e12-e352a3318be1": {
|
||||||
|
"name": "Groceries, obviously",
|
||||||
|
"icon": "cart",
|
||||||
|
"color": "pink",
|
||||||
|
"cookieStoreId": "firefox-container-591",
|
||||||
|
"macAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1"
|
||||||
|
},
|
||||||
|
"identity@@_3dc916fb-8c0a-4538-9758-73ef819a45f7": {
|
||||||
|
"name": "Facebook",
|
||||||
|
"icon": "fence",
|
||||||
|
"color": "toolbar",
|
||||||
|
"cookieStoreId": "firefox-container-592",
|
||||||
|
"macAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7"
|
||||||
|
},
|
||||||
|
"identity@@_63e5212f-0858-418e-b5a3-09c2dea61fcd": {
|
||||||
|
"name": "Oscar",
|
||||||
|
"icon": "dollar",
|
||||||
|
"color": "green",
|
||||||
|
"cookieStoreId": "firefox-container-593",
|
||||||
|
"macAddonUUID": "63e5212f-0858-418e-b5a3-09c2dea61fcd"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_developer.mozilla.org": {
|
||||||
|
"userContextId": "588",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e",
|
||||||
|
"hostname": "developer.mozilla.org"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_reddit.com": {
|
||||||
|
"userContextId": "592",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7",
|
||||||
|
"hostname": "reddit.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_twitter.com": {
|
||||||
|
"userContextId": "589",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187",
|
||||||
|
"hostname": "twitter.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_www.facebook.com": {
|
||||||
|
"userContextId": "590",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4",
|
||||||
|
"hostname": "www.facebook.com"
|
||||||
|
},
|
||||||
|
"siteContainerMap@@_www.linkedin.com": {
|
||||||
|
"userContextId": "591",
|
||||||
|
"neverAsk": true,
|
||||||
|
"identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1",
|
||||||
|
"hostname": "www.linkedin.com"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DUPE_TEST_LOCAL = {
|
||||||
|
"beenSynced": true,
|
||||||
|
"browserActionBadgesClicked": [
|
||||||
|
"6.2.0"
|
||||||
|
],
|
||||||
|
"containerTabsOpened": 7,
|
||||||
|
"identitiesState@@_firefox-default": {
|
||||||
|
"hiddenTabs": []
|
||||||
|
},
|
||||||
|
"onboarding-stage": 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DUPE_TEST_ASSIGNMENTS = [
|
||||||
|
"siteContainerMap@@_developer.mozilla.org",
|
||||||
|
"siteContainerMap@@_reddit.com",
|
||||||
|
"siteContainerMap@@_twitter.com",
|
||||||
|
"siteContainerMap@@_www.facebook.com",
|
||||||
|
"siteContainerMap@@_www.linkedin.com"
|
||||||
|
];
|
||||||
|
|
||||||
|
const SITE_ASSIGNMENT_TEST = [
|
||||||
|
"siteContainerMap@@_developer.mozilla.org",
|
||||||
|
"siteContainerMap@@_www.facebook.com",
|
||||||
|
"siteContainerMap@@_www.google.com",
|
||||||
|
"siteContainerMap@@_bugzilla.mozilla.org"
|
||||||
|
];
|
||||||
|
|
||||||
|
const DUPE_TEST_IDENTS = [
|
||||||
|
{
|
||||||
|
"name": "Personal",
|
||||||
|
"icon": "fingerprint",
|
||||||
|
"color": "blue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Banking",
|
||||||
|
"icon": "pet",
|
||||||
|
"color": "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mozilla",
|
||||||
|
"icon": "briefcase",
|
||||||
|
"color": "red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Groceries, obviously",
|
||||||
|
"icon": "cart",
|
||||||
|
"color": "orange",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Facebook",
|
||||||
|
"icon": "fence",
|
||||||
|
"color": "toolbar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Big Bird",
|
||||||
|
"icon": "dollar",
|
||||||
|
"color": "yellow",
|
||||||
|
}
|
||||||
|
];
|
|
@ -1,44 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
browser: {
|
|
||||||
async initializeWithTab(details = {
|
|
||||||
cookieStoreId: "firefox-default"
|
|
||||||
}) {
|
|
||||||
let tab;
|
|
||||||
await buildDom({
|
|
||||||
background: {
|
|
||||||
async afterBuild(background) {
|
|
||||||
tab = await background.browser.tabs._create(details);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
popup: {
|
|
||||||
jsdom: {
|
|
||||||
beforeParse(window) {
|
|
||||||
window.browser.storage.local.set({
|
|
||||||
"browserActionBadgesClicked": [],
|
|
||||||
"onboarding-stage": 5,
|
|
||||||
"achievements": []
|
|
||||||
});
|
|
||||||
window.browser.storage.local.set.resetHistory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tab;
|
|
||||||
},
|
|
||||||
|
|
||||||
async openNewTab(tab, options = {}) {
|
|
||||||
return background.browser.tabs._create(tab, options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
popup: {
|
|
||||||
async clickElementById(id) {
|
|
||||||
await popup.helper.clickElementById(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
async clickLastMatchingElementByQuerySelector(querySelector) {
|
|
||||||
await popup.helper.clickElementByQuerySelectorAll(querySelector, "last");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,16 +1,19 @@
|
||||||
describe("#1168", () => {
|
const {expect, sinon, initializeWithTab} = require("../common");
|
||||||
describe("when navigation happens too slow after opening new tab to a page which then redirects", () => {
|
|
||||||
let clock, tab;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
describe("#1168", function () {
|
||||||
await helper.browser.initializeWithTab({
|
describe("when navigation happens too slow after opening new tab to a page which then redirects", function () {
|
||||||
|
let clock, tab, background;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.webExt = await initializeWithTab({
|
||||||
cookieStoreId: "firefox-container-1",
|
cookieStoreId: "firefox-container-1",
|
||||||
url: "https://bugzilla.mozilla.org"
|
url: "https://bugzilla.mozilla.org"
|
||||||
});
|
});
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
|
||||||
|
await this.webExt.popup.helper.clickElementById("container-page-assigned");
|
||||||
|
|
||||||
clock = sinon.useFakeTimers();
|
clock = sinon.useFakeTimers();
|
||||||
tab = await helper.browser.openNewTab({});
|
tab = await this.webExt.browser.tabs._create({});
|
||||||
|
|
||||||
clock.tick(2000);
|
clock.tick(2000);
|
||||||
|
|
||||||
|
@ -20,15 +23,16 @@ describe("#1168", () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
this.webExt.destroy();
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
// Not solved yet
|
// Not solved yet
|
||||||
// See: https://github.com/mozilla/multi-account-containers/issues/1168#issuecomment-378394091
|
// See: https://github.com/mozilla/multi-account-containers/issues/1168#issuecomment-378394091
|
||||||
it.skip("should remove the old tab", async () => {
|
it.skip("should remove the old tab", async function () {
|
||||||
expect(background.browser.tabs.create).to.have.been.calledOnce;
|
expect(background.browser.tabs.create).to.have.been.calledOnce;
|
||||||
expect(background.browser.tabs.remove).to.have.been.calledWith(tab.id);
|
expect(background.browser.tabs.remove).to.have.been.calledWith(tab.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
clock.restore();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,15 +1,18 @@
|
||||||
describe("#940", () => {
|
const {expect, sinon, initializeWithTab} = require("../common");
|
||||||
describe("when other onBeforeRequestHandlers are faster and redirect with the same requestId", () => {
|
|
||||||
it("should not open two confirm pages", async () => {
|
describe("#940", function () {
|
||||||
await helper.browser.initializeWithTab({
|
describe("when other onBeforeRequestHandlers are faster and redirect with the same requestId", function () {
|
||||||
|
it("should not open two confirm pages", async function () {
|
||||||
|
const webExtension = await initializeWithTab({
|
||||||
cookieStoreId: "firefox-container-1",
|
cookieStoreId: "firefox-container-1",
|
||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
});
|
});
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
|
||||||
|
await webExtension.popup.helper.clickElementById("container-page-assigned");
|
||||||
|
|
||||||
const responses = {};
|
const responses = {};
|
||||||
await helper.browser.openNewTab({
|
await webExtension.background.browser.tabs._create({
|
||||||
url: "http://example.com"
|
url: "https://example.com"
|
||||||
}, {
|
}, {
|
||||||
options: {
|
options: {
|
||||||
webRequestRedirects: ["https://example.com"],
|
webRequestRedirects: ["https://example.com"],
|
||||||
|
@ -23,46 +26,55 @@ describe("#940", () => {
|
||||||
expect(result).to.deep.equal({
|
expect(result).to.deep.equal({
|
||||||
cancel: true
|
cancel: true
|
||||||
});
|
});
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
webExtension.browser.tabs.create.should.have.been.calledOnce;
|
||||||
|
|
||||||
|
webExtension.destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when redirects change requestId midflight", () => {
|
describe("when redirects change requestId midflight", function () {
|
||||||
let newTab;
|
beforeEach(async function () {
|
||||||
const newTabResponses = {};
|
|
||||||
const redirectedRequest = async (options = {}) => {
|
|
||||||
global.clock = sinon.useFakeTimers();
|
|
||||||
newTab = await helper.browser.openNewTab({
|
|
||||||
url: "http://youtube.com"
|
|
||||||
}, {
|
|
||||||
options: Object.assign({
|
|
||||||
webRequestRedirects: [
|
|
||||||
"https://youtube.com",
|
|
||||||
"https://www.youtube.com",
|
|
||||||
{
|
|
||||||
url: "https://www.youtube.com",
|
|
||||||
webRequest: {
|
|
||||||
requestId: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
webRequestError: true,
|
|
||||||
instantRedirects: true
|
|
||||||
}, options),
|
|
||||||
responses: newTabResponses
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
this.webExt = await initializeWithTab({
|
||||||
await helper.browser.initializeWithTab({
|
|
||||||
cookieStoreId: "firefox-container-1",
|
cookieStoreId: "firefox-container-1",
|
||||||
url: "https://www.youtube.com"
|
url: "https://www.youtube.com"
|
||||||
});
|
});
|
||||||
await helper.popup.clickElementById("container-page-assigned");
|
await this.webExt.popup.helper.clickElementById("container-page-assigned");
|
||||||
|
|
||||||
|
global.clock = sinon.useFakeTimers();
|
||||||
|
this.redirectedRequest = async (options = {}) => {
|
||||||
|
const newTabResponses = {};
|
||||||
|
const newTab = await this.webExt.browser.tabs._create({
|
||||||
|
url: "http://youtube.com"
|
||||||
|
}, {
|
||||||
|
options: Object.assign({
|
||||||
|
webRequestRedirects: [
|
||||||
|
"https://youtube.com",
|
||||||
|
"https://www.youtube.com",
|
||||||
|
{
|
||||||
|
url: "https://www.youtube.com",
|
||||||
|
webRequest: {
|
||||||
|
requestId: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
webRequestError: true,
|
||||||
|
instantRedirects: true
|
||||||
|
}, options),
|
||||||
|
responses: newTabResponses
|
||||||
|
});
|
||||||
|
|
||||||
|
return [newTabResponses, newTab];
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not open two confirm pages", async () => {
|
afterEach(function () {
|
||||||
await redirectedRequest();
|
this.webExt.destroy();
|
||||||
|
global.clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not open two confirm pages", async function () {
|
||||||
|
const [newTabResponses] = await this.redirectedRequest();
|
||||||
|
|
||||||
// http://youtube.com is not assigned, no cancel, no reopening
|
// http://youtube.com is not assigned, no cancel, no reopening
|
||||||
expect(await newTabResponses.webRequest.onBeforeRequest[0]).to.deep.equal({});
|
expect(await newTabResponses.webRequest.onBeforeRequest[0]).to.deep.equal({});
|
||||||
|
@ -80,17 +92,17 @@ describe("#940", () => {
|
||||||
cancel: true
|
cancel: true
|
||||||
});
|
});
|
||||||
|
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should uncancel after webRequest.onCompleted", async () => {
|
it("should uncancel after webRequest.onCompleted", async function () {
|
||||||
await redirectedRequest();
|
const [newTabResponses, newTab] = await this.redirectedRequest();
|
||||||
// remove onCompleted listeners because in the real world this request would never complete
|
// remove onCompleted listeners because in the real world this request would never complete
|
||||||
// and thus might trigger unexpected behavior because the tab gets removed when reopening
|
// and thus might trigger unexpected behavior because the tab gets removed when reopening
|
||||||
background.browser.webRequest.onCompleted.addListener = sinon.stub();
|
this.webExt.background.browser.webRequest.onCompleted.addListener = sinon.stub();
|
||||||
background.browser.tabs.create.resetHistory();
|
this.webExt.background.browser.tabs.create.resetHistory();
|
||||||
// we create a tab with the same id and use the same request id to see if uncanceled
|
// we create a tab with the same id and use the same request id to see if uncanceled
|
||||||
await helper.browser.openNewTab({
|
await this.webExt.browser.tabs._create({
|
||||||
id: newTab.id,
|
id: newTab.id,
|
||||||
url: "https://www.youtube.com"
|
url: "https://www.youtube.com"
|
||||||
}, {
|
}, {
|
||||||
|
@ -101,14 +113,14 @@ describe("#940", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should uncancel after webRequest.onErrorOccurred", async () => {
|
it("should uncancel after webRequest.onErrorOccurred", async function () {
|
||||||
await redirectedRequest();
|
const [newTabResponses, newTab] = await this.redirectedRequest();
|
||||||
background.browser.tabs.create.resetHistory();
|
this.webExt.background.browser.tabs.create.resetHistory();
|
||||||
// we create a tab with the same id and use the same request id to see if uncanceled
|
// we create a tab with the same id and use the same request id to see if uncanceled
|
||||||
await helper.browser.openNewTab({
|
await this.webExt.browser.tabs._create({
|
||||||
id: newTab.id,
|
id: newTab.id,
|
||||||
url: "https://www.youtube.com"
|
url: "https://www.youtube.com"
|
||||||
}, {
|
}, {
|
||||||
|
@ -120,18 +132,18 @@ describe("#940", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should uncancel after 2 seconds", async () => {
|
it("should uncancel after 2 seconds", async function () {
|
||||||
await redirectedRequest({
|
const [newTabResponses, newTab] = await this.redirectedRequest({
|
||||||
webRequestDontYield: ["onCompleted", "onErrorOccurred"]
|
webRequestDontYield: ["onCompleted", "onErrorOccurred"]
|
||||||
});
|
});
|
||||||
global.clock.tick(2000);
|
global.clock.tick(2000);
|
||||||
|
|
||||||
background.browser.tabs.create.resetHistory();
|
this.webExt.background.browser.tabs.create.resetHistory();
|
||||||
// we create a tab with the same id and use the same request id to see if uncanceled
|
// we create a tab with the same id and use the same request id to see if uncanceled
|
||||||
await helper.browser.openNewTab({
|
await this.webExt.browser.tabs._create({
|
||||||
id: newTab.id,
|
id: newTab.id,
|
||||||
url: "https://www.youtube.com"
|
url: "https://www.youtube.com"
|
||||||
}, {
|
}, {
|
||||||
|
@ -143,13 +155,13 @@ describe("#940", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not influence the canceled url in other tabs", async () => {
|
it("should not influence the canceled url in other tabs", async function () {
|
||||||
await redirectedRequest();
|
await this.redirectedRequest();
|
||||||
background.browser.tabs.create.resetHistory();
|
this.webExt.background.browser.tabs.create.resetHistory();
|
||||||
await helper.browser.openNewTab({
|
await this.webExt.browser.tabs._create({
|
||||||
cookieStoreId: "firefox-default",
|
cookieStoreId: "firefox-default",
|
||||||
url: "https://www.youtube.com"
|
url: "https://www.youtube.com"
|
||||||
}, {
|
}, {
|
||||||
|
@ -158,11 +170,7 @@ describe("#940", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
background.browser.tabs.create.should.have.been.calledOnce;
|
this.webExt.background.browser.tabs.create.should.have.been.calledOnce;
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
global.clock.restore();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
if (!process.listenerCount("unhandledRejection")) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
process.on("unhandledRejection", r => console.log(r));
|
|
||||||
}
|
|
||||||
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 webExtensionsJSDOM = require("webextensions-jsdom");
|
|
||||||
const manifestPath = path.resolve(path.join(__dirname, "../src/manifest.json"));
|
|
||||||
|
|
||||||
global.buildDom = async ({background = {}, popup = {}}) => {
|
|
||||||
background = {
|
|
||||||
...background,
|
|
||||||
jsdom: {
|
|
||||||
...background.jsom,
|
|
||||||
beforeParse(window) {
|
|
||||||
window.browser.permissions.getAll.resolves({permissions: ["bookmarks"]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
popup = {
|
|
||||||
...popup,
|
|
||||||
jsdom: {
|
|
||||||
...popup.jsdom,
|
|
||||||
pretendToBeVisual: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const webExtension = await webExtensionsJSDOM.fromManifest(manifestPath, {
|
|
||||||
apiFake: true,
|
|
||||||
wiring: true,
|
|
||||||
sinon: global.sinon,
|
|
||||||
background,
|
|
||||||
popup
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line require-atomic-updates
|
|
||||||
global.background = webExtension.background;
|
|
||||||
// eslint-disable-next-line require-atomic-updates
|
|
||||||
global.popup = webExtension.popup;
|
|
||||||
};
|
|
||||||
|
|
||||||
global.buildBackgroundDom = async background => {
|
|
||||||
await global.buildDom({
|
|
||||||
background,
|
|
||||||
popup: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
global.buildPopupDom = async popup => {
|
|
||||||
await global.buildDom({
|
|
||||||
popup,
|
|
||||||
background: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
global.afterEach(() => {
|
|
||||||
if (global.background) {
|
|
||||||
global.background.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (global.popup) {
|
|
||||||
global.popup.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
Add table
Reference in a new issue