Break web extensions code
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
const DEFAULT_TAB = "about:newtab";
|
||||
const backgroundLogic = {
|
||||
NEW_TAB_PAGES: new Set([
|
||||
"about:startpage",
|
||||
"about:newtab",
|
||||
"about:home",
|
||||
"about:blank"
|
||||
]),
|
||||
|
||||
async getExtensionInfo() {
|
||||
const manifestPath = browser.extension.getURL("manifest.json");
|
||||
const response = await fetch(manifestPath);
|
||||
const extensionInfo = await response.json();
|
||||
return extensionInfo;
|
||||
},
|
||||
|
||||
getUserContextIdFromCookieStoreId(cookieStoreId) {
|
||||
if (!cookieStoreId) {
|
||||
return false;
|
||||
}
|
||||
const container = cookieStoreId.replace("firefox-container-", "");
|
||||
if (container !== cookieStoreId) {
|
||||
return container;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async deleteContainer(userContextId) {
|
||||
this.sendTelemetryPayload({
|
||||
event: "delete-container",
|
||||
userContextId
|
||||
});
|
||||
|
||||
await this._closeTabs(userContextId);
|
||||
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId));
|
||||
assignManager.deleteContainer(userContextId);
|
||||
await browser.runtime.sendMessage({
|
||||
method: "forgetIdentityAndRefresh"
|
||||
});
|
||||
return {done: true, userContextId};
|
||||
},
|
||||
|
||||
async createOrUpdateContainer(options) {
|
||||
let donePromise;
|
||||
if (options.userContextId !== "new") {
|
||||
donePromise = browser.contextualIdentities.update(
|
||||
this.cookieStoreId(options.userContextId),
|
||||
options.params
|
||||
);
|
||||
this.sendTelemetryPayload({
|
||||
event: "edit-container",
|
||||
userContextId: options.userContextId
|
||||
});
|
||||
} else {
|
||||
donePromise = browser.contextualIdentities.create(options.params);
|
||||
this.sendTelemetryPayload({
|
||||
event: "add-container"
|
||||
});
|
||||
}
|
||||
await donePromise;
|
||||
browser.runtime.sendMessage({
|
||||
method: "refreshNeeded"
|
||||
});
|
||||
},
|
||||
|
||||
async openTab(options) {
|
||||
let url = options.url || undefined;
|
||||
const userContextId = ("userContextId" in options) ? options.userContextId : 0;
|
||||
const active = ("nofocus" in options) ? options.nofocus : true;
|
||||
const source = ("source" in options) ? options.source : null;
|
||||
|
||||
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
|
||||
// Only send telemetry for tabs opened by UI - i.e., not via showTabs
|
||||
if (source && userContextId) {
|
||||
this.sendTelemetryPayload({
|
||||
"event": "open-tab",
|
||||
"eventSource": source,
|
||||
"userContextId": userContextId,
|
||||
"clickedContainerTabCount": await identityState.containerTabCount(cookieStoreId)
|
||||
});
|
||||
}
|
||||
// Autofocus url bar will happen in 54: https://bugzilla.mozilla.org/show_bug.cgi?id=1295072
|
||||
|
||||
// We can't open new tab pages, so open a blank tab. Used in tab un-hide
|
||||
if (this.NEW_TAB_PAGES.has(url)) {
|
||||
url = undefined;
|
||||
}
|
||||
|
||||
// Unhide all hidden tabs
|
||||
this.showTabs({
|
||||
cookieStoreId
|
||||
});
|
||||
return browser.tabs.create({
|
||||
url,
|
||||
active,
|
||||
pinned: options.pinned || false,
|
||||
cookieStoreId
|
||||
});
|
||||
},
|
||||
|
||||
async getTabs(options) {
|
||||
if (!("cookieStoreId" in options)) {
|
||||
return new Error("getTabs must be called with cookieStoreId argument.");
|
||||
}
|
||||
|
||||
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
|
||||
await identityState.remapTabsIfMissing(options.cookieStoreId);
|
||||
const isKnownContainer = await identityState._isKnownContainer(userContextId);
|
||||
if (!isKnownContainer) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const list = [];
|
||||
const tabs = await this._containerTabs(options.cookieStoreId);
|
||||
tabs.forEach((tab) => {
|
||||
list.push(identityState._createTabObject(tab));
|
||||
});
|
||||
|
||||
const containerState = await identityState.storageArea.get(options.cookieStoreId);
|
||||
return list.concat(containerState.hiddenTabs);
|
||||
},
|
||||
|
||||
async moveTabsToWindow(options) {
|
||||
if (!("cookieStoreId" in options)) {
|
||||
return new Error("moveTabsToWindow must be called with cookieStoreId argument.");
|
||||
}
|
||||
|
||||
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
|
||||
await identityState.remapTabsIfMissing(options.cookieStoreId);
|
||||
if (!identityState._isKnownContainer(userContextId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.sendTelemetryPayload({
|
||||
"event": "move-tabs-to-window",
|
||||
"userContextId": userContextId,
|
||||
"clickedContainerTabCount": identityState.containerTabCount(userContextId),
|
||||
});
|
||||
|
||||
const list = await identityState._matchTabsByContainer(options.cookieStoreId);
|
||||
|
||||
const containerState = await identityState.storageArea.get(options.cookieStoreId);
|
||||
// Nothing to do
|
||||
if (list.length === 0 &&
|
||||
containerState.hiddenTabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
const window = await browser.windows.create({
|
||||
tabId: list.shift().id
|
||||
});
|
||||
browser.tabs.move(list, {
|
||||
windowId: window.id,
|
||||
index: -1
|
||||
});
|
||||
|
||||
// Let's show the hidden tabs.
|
||||
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
|
||||
browser.tabs.create(object.url || DEFAULT_TAB, {
|
||||
windowId: window.id,
|
||||
cookieStoreId: options.cookieStoreId
|
||||
});
|
||||
}
|
||||
|
||||
containerState.hiddenTabs = [];
|
||||
|
||||
// Let's close all the normal tab in the new window. In theory it
|
||||
// should be only the first tab, but maybe there are addons doing
|
||||
// crazy stuff.
|
||||
const tabs = browser.tabs.query({windowId: window.id});
|
||||
for (let tab of tabs) { // eslint-disable-line prefer-const
|
||||
if (tabs.cookieStoreId !== options.cookieStoreId) {
|
||||
browser.tabs.remove(tab.id);
|
||||
}
|
||||
}
|
||||
return await identityState.storageArea.set(options.cookieStoreId, containerState);
|
||||
},
|
||||
|
||||
async _closeTabs(userContextId) {
|
||||
const cookieStoreId = this.cookieStoreId(userContextId);
|
||||
const tabs = await this._containerTabs(cookieStoreId);
|
||||
const tabIds = tabs.map((tab) => tab.id);
|
||||
return browser.tabs.remove(tabIds);
|
||||
},
|
||||
|
||||
async queryIdentitiesState() {
|
||||
const identities = await browser.contextualIdentities.query({});
|
||||
const identitiesOutput = {};
|
||||
const identitiesPromise = identities.map(async function (identity) {
|
||||
await identityState.remapTabsIfMissing(identity.cookieStoreId);
|
||||
const containerState = await identityState.storageArea.get(identity.cookieStoreId);
|
||||
identitiesOutput[identity.cookieStoreId] = {
|
||||
hasHiddenTabs: !!containerState.hiddenTabs.length,
|
||||
hasOpenTabs: !!containerState.openTabs
|
||||
};
|
||||
return;
|
||||
});
|
||||
await Promise.all(identitiesPromise);
|
||||
return identitiesOutput;
|
||||
},
|
||||
|
||||
async sortTabs() {
|
||||
const containersCounts = identityState.containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "sort-tabs",
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"totalContainerTabsCount": await identityState.totalContainerTabsCount(),
|
||||
"totalNonContainerTabsCount": await identityState.totalNonContainerTabsCount()
|
||||
});
|
||||
const windows = await browser.windows.getAll();
|
||||
for (let window of windows) { // eslint-disable-line prefer-const
|
||||
// First the pinned tabs, then the normal ones.
|
||||
await this._sortTabsInternal(window, true);
|
||||
await this._sortTabsInternal(window, false);
|
||||
}
|
||||
},
|
||||
|
||||
async _sortTabsInternal(window, pinnedTabs) {
|
||||
const tabs = await browser.tabs.query({windowId: window.id});
|
||||
let pos = 0;
|
||||
|
||||
// Let's collect UCIs/tabs for this window.
|
||||
const map = new Map;
|
||||
for (const tab of tabs) {
|
||||
if (pinnedTabs && !tab.pinned) {
|
||||
// We don't have, or we already handled all the pinned tabs.
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pinnedTabs && tab.pinned) {
|
||||
// pinned tabs must be consider as taken positions.
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
|
||||
if (!map.has(userContextId)) {
|
||||
map.set(userContextId, []);
|
||||
}
|
||||
map.get(userContextId).push(tab);
|
||||
}
|
||||
|
||||
// Let's sort the map.
|
||||
const sortMap = new Map([...map.entries()].sort((a, b) => a[0] > b[0]));
|
||||
|
||||
// Let's move tabs.
|
||||
sortMap.forEach(tabs => {
|
||||
for (const tab of tabs) {
|
||||
++pos;
|
||||
browser.tabs.move(tab.id, {
|
||||
windowId: window.id,
|
||||
index: pos
|
||||
});
|
||||
//xulWindow.gBrowser.moveTabTo(tab, pos++);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async hideTabs(options) {
|
||||
if (!("cookieStoreId" in options)) {
|
||||
return new Error("hideTabs must be called with cookieStoreId option.");
|
||||
}
|
||||
|
||||
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
|
||||
await identityState.remapTabsIfMissing(options.cookieStoreId);
|
||||
const isKnownContainer = await identityState._isKnownContainer(userContextId);
|
||||
if (!isKnownContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const containersCounts = identityState.containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "hide-tabs",
|
||||
"userContextId": userContextId,
|
||||
"clickedContainerTabCount": identityState.containerTabCount(userContextId),
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"hiddenContainersCount": containersCounts.hidden,
|
||||
"totalContainersCount": containersCounts.total
|
||||
});
|
||||
|
||||
const containerState = await identityState.storeHidden(options.cookieStoreId);
|
||||
await this._closeTabs(userContextId);
|
||||
return containerState;
|
||||
},
|
||||
|
||||
async showTabs(options) {
|
||||
if (!("cookieStoreId" in options)) {
|
||||
return Promise.reject("showTabs must be called with cookieStoreId argument.");
|
||||
}
|
||||
|
||||
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
|
||||
await identityState.remapTabsIfMissing(options.cookieStoreId);
|
||||
if (!identityState._isKnownContainer(userContextId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const containersCounts = identityState.containersCounts();
|
||||
this.sendTelemetryPayload({
|
||||
"event": "show-tabs",
|
||||
"userContextId": userContextId,
|
||||
"clickedContainerTabCount": await identityState.containerTabCount(options.cookieStoreId),
|
||||
"shownContainersCount": containersCounts.shown,
|
||||
"hiddenContainersCount": containersCounts.hidden,
|
||||
"totalContainersCount": containersCounts.total
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
|
||||
const containerState = await identityState.storageArea.get(options.cookieStoreId);
|
||||
|
||||
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
|
||||
promises.push(this.openTab({
|
||||
userContextId: userContextId,
|
||||
url: object.url,
|
||||
nofocus: options.nofocus || false,
|
||||
pinned: object.pinned,
|
||||
}));
|
||||
}
|
||||
|
||||
containerState.hiddenTabs = [];
|
||||
|
||||
await Promise.all(promises);
|
||||
return await identityState.storageArea.set(options.cookieStoreId, containerState);
|
||||
},
|
||||
|
||||
|
||||
sendTelemetryPayload(message = {}) {
|
||||
if (!message.event) {
|
||||
throw new Error("Missing event name for telemetry");
|
||||
}
|
||||
message.method = "sendTelemetryPayload";
|
||||
//TODO decide where this goes
|
||||
// browser.runtime.sendMessage(message);
|
||||
},
|
||||
|
||||
cookieStoreId(userContextId) {
|
||||
return `firefox-container-${userContextId}`;
|
||||
},
|
||||
|
||||
_containerTabs(cookieStoreId) {
|
||||
return browser.tabs.query({
|
||||
cookieStoreId
|
||||
}).catch((e) => {throw e;});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user