aboutsummaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
authorAlessio Vanni <vannilla@firemail.cc>2019-06-21 22:03:03 +0200
committerAlessio Vanni <vannilla@firemail.cc>2019-06-21 22:03:03 +0200
commitbdc82e65c4d30f541b5e0efcd3c0723103435c4e (patch)
tree43bbac3804e4fd006e75779b2537164ea1b9f91c /js
parent23f2978b8a41649c93f8b46c0f967f8b226928a4 (diff)
downloadematrix-bdc82e65c4d30f541b5e0efcd3c0723103435c4e.tar.lz
ematrix-bdc82e65c4d30f541b5e0efcd3c0723103435c4e.tar.xz
ematrix-bdc82e65c4d30f541b5e0efcd3c0723103435c4e.zip
Split tab handling from vapi-background
That file is too large, let's split it up.
Diffstat (limited to 'js')
-rw-r--r--js/vapi-background.js712
-rw-r--r--js/vapi-tabs.js735
2 files changed, 743 insertions, 704 deletions
diff --git a/js/vapi-background.js b/js/vapi-background.js
index f861338..d939186 100644
--- a/js/vapi-background.js
+++ b/js/vapi-background.js
@@ -90,6 +90,14 @@
// eMatrix: do we?
let expectedNumberOfCleanups = 7;
+ vAPI.addCleanUpTask = function (task) {
+ if (typeof task !== 'function') {
+ return;
+ }
+
+ cleanupTasks.push(task);
+ }
+
window.addEventListener('unload', function () {
// if (typeof vAPI.app.onShutdown === 'function') {
// vAPI.app.onShutdown();
@@ -770,710 +778,6 @@
return tabId.toString() === '-1';
};
- vAPI.noTabId = '-1';
-
- // Tabs and related functions
- vAPI.tabs = {};
-
- vAPI.tabs.registerListeners = function() {
- tabWatcher.start();
- };
-
- vAPI.tabs.get = function (tabId, callback) {
- // eMatrix: the following might be obsoleted (though probably
- // still relevant at least for Pale Moon.)
- //
- // Firefox:
- //
- // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+
- // ^ |
- // | |
- // +--------------------------------------------------------------+
- //
- // browser (browser)
- // contentTitle
- // currentURI
- // ownerDocument (XULDocument)
- // defaultView (ChromeWindow)
- // gBrowser (tabbrowser OR browser)
- // browsers (browser)
- // selectedBrowser
- // selectedTab
- // tabs (tab.tabbrowser-tab)
- //
- // Fennec: (what I figured so far)
- //
- // tab -> browser windows -> window -> BrowserApp -> tabs --+
- // ^ window |
- // | |
- // +-----------------------------------------------------------+
- //
- // tab
- // browser
- // [manual search to go back to tab from list of windows]
- let browser;
-
- if (tabId === null) {
- browser = tabWatcher.currentBrowser();
- tabId = tabWatcher.tabIdFromTarget(browser);
- } else {
- browser = tabWatcher.browserFromTabId(tabId);
- }
-
- // For internal use
- if (typeof callback !== 'function') {
- return browser;
- }
-
- if (!browser || !browser.currentURI) {
- callback();
- return;
- }
-
- let win = getOwnerWindow(browser);
- let tabBrowser = getTabBrowser(win);
-
- callback({
- id: tabId,
- windowId: winWatcher.idFromWindow(win),
- active: tabBrowser !== null
- && browser === tabBrowser.selectedBrowser,
- url: browser.currentURI.asciiSpec,
- title: browser.contentTitle
- });
- };
-
- vAPI.tabs.getAllSync = function (window) {
- let win;
- let tabs = [];
-
- for (let win of winWatcher.getWindows()) {
- if (window && window !== win) {
- continue;
- }
-
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- continue;
- }
-
- // This can happens if a tab-less window is currently opened.
- // Example of a tab-less window: one opened from clicking
- // "View Page Source".
- if (!tabBrowser.tabs) {
- continue;
- }
-
- for (let tab of tabBrowser.tabs) {
- tabs.push(tab);
- }
- }
-
- return tabs;
- };
-
- vAPI.tabs.getAll = function (callback) {
- let tabs = [];
-
- for (let browser of tabWatcher.browsers()) {
- let tab = tabWatcher.tabFromBrowser(browser);
-
- if (tab === null) {
- continue;
- }
-
- if (tab.hasAttribute('pending')) {
- continue;
- }
-
- tabs.push({
- id: tabWatcher.tabIdFromTarget(browser),
- url: browser.currentURI.asciiSpec
- });
- }
-
- callback(tabs);
- };
-
- vAPI.tabs.open = function (details) {
- // properties of the details object:
- // + url - the address that will be opened
- // + tabId:- the tab is used if set, instead of creating a new one
- // + index: - undefined: end of the list, -1: following tab, or
- // after index
- // + active: - opens the tab in background - true and undefined:
- // foreground
- // + select: - if a tab is already opened with that url, then
- // select it instead of opening a new one
- if (!details.url) {
- return null;
- }
-
- // extension pages
- if (/^[\w-]{2,}:/.test(details.url) === false) {
- details.url = vAPI.getURL(details.url);
- }
-
- if (details.select) {
- let URI = Services.io.newURI(details.url, null, null);
-
- for (let tab of this.getAllSync()) {
- let browser = tabWatcher.browserFromTarget(tab);
-
- // https://github.com/gorhill/uBlock/issues/2558
- if (browser === null) {
- continue;
- }
-
- // Or simply .equals if we care about the fragment
- if (URI.equalsExceptRef(browser.currentURI) === false) {
- continue;
- }
-
- this.select(tab);
-
- // Update URL if fragment is different
- if (URI.equals(browser.currentURI) === false) {
- browser.loadURI(URI.asciiSpec);
- }
-
- return;
- }
- }
-
- if (details.active === undefined) {
- details.active = true;
- }
-
- if (details.tabId) {
- let tab = tabWatcher.browserFromTabId(details.tabId);
-
- if (tab) {
- tabWatcher.browserFromTarget(tab).loadURI(details.url);
- return;
- }
- }
-
- // Open in a standalone window
- if (details.popup === true) {
- Services.ww.openWindow(self,
- details.url,
- null,
- 'location=1,menubar=1,personalbar=1,'
- +'resizable=1,toolbar=1',
- null);
- return;
- }
-
- let win = winWatcher.getCurrentWindow();
- let tabBrowser = getTabBrowser(win);
-
- if (tabBrowser === null) {
- return;
- }
-
- if (details.index === -1) {
- details.index =
- tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1;
- }
-
- let tab = tabBrowser.loadOneTab(details.url, {
- inBackground: !details.active
- });
-
- if (details.index !== undefined) {
- tabBrowser.moveTabTo(tab, details.index);
- }
- };
-
- vAPI.tabs.replace = function (tabId, url) {
- // Replace the URL of a tab. Noop if the tab does not exist.
- let targetURL = url;
-
- // extension pages
- if (/^[\w-]{2,}:/.test(targetURL) !== true) {
- targetURL = vAPI.getURL(targetURL);
- }
-
- let browser = tabWatcher.browserFromTabId(tabId);
- if (browser) {
- browser.loadURI(targetURL);
- }
- };
-
- vAPI.tabs._remove = function (tab, tabBrowser) {
- if (tabBrowser) {
- tabBrowser.removeTab(tab);
- }
- };
- function removeInternal(tab, tabBrowser) {
- if (tabBrowser) {
- tabBrowser.removeTab(tab);
- }
- }
-
- vAPI.tabs.remove = function (tabId) {
- let browser = tabWatcher.browserFromTabId(tabId);
- if (!browser) {
- return;
- }
-
- let tab = tabWatcher.tabFromBrowser(browser);
- if (!tab) {
- return;
- }
-
- removeInternal(tab, getTabBrowser(getOwnerWindow(browser)));
- };
-
- vAPI.tabs.reload = function (tabId) {
- let browser = tabWatcher.browserFromTabId(tabId);
- if (!browser) {
- return;
- }
-
- browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
- };
-
- vAPI.tabs.select = function (tab) {
- if (typeof tab !== 'object') {
- tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab));
- }
- if (!tab) {
- return;
- }
-
- // https://github.com/gorhill/uBlock/issues/470
- let win = getOwnerWindow(tab);
- win.focus();
-
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser) {
- tabBrowser.selectedTab = tab;
- }
- };
-
- vAPI.tabs.injectScript = function (tabId, details, callback) {
- let browser = tabWatcher.browserFromTabId(tabId);
- if (!browser) {
- return;
- }
-
- if (typeof details.file !== 'string') {
- return;
- }
-
- details.file = vAPI.getURL(details.file);
- browser.messageManager.sendAsyncMessage(location.host + ':broadcast',
- JSON.stringify({
- broadcast: true,
- channelName: 'vAPI',
- msg: {
- cmd: 'injectScript',
- details: details
- }
- }));
-
- if (typeof callback === 'function') {
- vAPI.setTimeout(callback, 13);
- }
- };
-
- let tabWatcher = (function () {
- // TODO: find out whether we need a janitor to take care of stale entries.
-
- // https://github.com/gorhill/uMatrix/issues/540
- // Use only weak references to hold onto browser references.
- let browserToTabIdMap = new WeakMap();
- let tabIdToBrowserMap = new Map();
- let tabIdGenerator = 1;
-
- let indexFromBrowser = function (browser) {
- if (!browser) {
- return -1;
- }
-
- let win = getOwnerWindow(browser);
- if (!win) {
- return -1;
- }
-
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- return -1;
- }
-
- // This can happen, for example, the `view-source:`
- // window, there is no tabbrowser object, the browser
- // object sits directly in the window.
- if (tabBrowser === browser) {
- return 0;
- }
-
- return tabBrowser.browsers.indexOf(browser);
- };
-
- let indexFromTarget = function (target) {
- return indexFromBrowser(browserFromTarget(target));
- };
-
- let tabFromBrowser = function (browser) {
- let i = indexFromBrowser(browser);
- if (i === -1) {
- return null;
- }
-
- let win = getOwnerWindow(browser);
- if (!win) {
- return null;
- }
-
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- return null;
- }
-
- if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) {
- return null;
- }
-
- return tabBrowser.tabs[i];
- };
-
- let browserFromTarget = function (target) {
- if (!target) {
- return null;
- }
-
- if (target.linkedPanel) {
- // target is a tab
- target = target.linkedBrowser;
- }
-
- if (target.localName !== 'browser') {
- return null;
- }
-
- return target;
- };
-
- let tabIdFromTarget = function (target) {
- let browser = browserFromTarget(target);
- if (browser === null) {
- return vAPI.noTabId;
- }
-
- let tabId = browserToTabIdMap.get(browser);
- if (tabId === undefined) {
- tabId = '' + tabIdGenerator++;
- browserToTabIdMap.set(browser, tabId);
- tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser));
- }
-
- return tabId;
- };
-
- let browserFromTabId = function (tabId) {
- let weakref = tabIdToBrowserMap.get(tabId);
- let browser = weakref && weakref.get();
-
- return browser || null;
- };
-
- let currentBrowser = function () {
- let win = winWatcher.getCurrentWindow();
-
- // https://github.com/gorhill/uBlock/issues/399
- // getTabBrowser() can return null at browser launch time.
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- return null;
- }
-
- return browserFromTarget(tabBrowser.selectedTab);
- };
-
- let removeBrowserEntry = function (tabId, browser) {
- if (tabId && tabId !== vAPI.noTabId) {
- vAPI.tabs.onClosed(tabId);
- delete vAPI.toolbarButton.tabs[tabId];
- tabIdToBrowserMap.delete(tabId);
- }
-
- if (browser) {
- browserToTabIdMap.delete(browser);
- }
- };
-
- let removeTarget = function (target) {
- onClose({
- target: target
- });
- };
-
- let getAllBrowsers = function () {
- let browsers = [];
-
- for (let [tabId, weakref] of tabIdToBrowserMap) {
- let browser = weakref.get();
-
- // TODO: Maybe call removeBrowserEntry() if the
- // browser no longer exists?
- if (browser) {
- browsers.push(browser);
- }
- }
-
- return browsers;
- };
-
- // var onOpen = function (target) {
- // var tabId = tabIdFromTarget(target);
- // var browser = browserFromTabId(tabId);
- // vAPI.tabs.onNavigation({
- // frameId: 0,
- // tabId: tabId,
- // url: browser.currentURI.asciiSpec,
- // });
- // };
-
- var onShow = function ({target}) {
- tabIdFromTarget(target);
- };
-
- var onClose = function ({target}) {
- // target is tab in Firefox, browser in Fennec
- let browser = browserFromTarget(target);
- let tabId = browserToTabIdMap.get(browser);
- removeBrowserEntry(tabId, browser);
- };
-
- var onSelect = function ({target}) {
- // This is an entry point: when creating a new tab, it is
- // not always reported through onLocationChanged...
- // Sigh. It is "reported" here however.
- let browser = browserFromTarget(target);
- let tabId = browserToTabIdMap.get(browser);
-
- if (tabId === undefined) {
- tabId = tabIdFromTarget(target);
- vAPI.tabs.onNavigation({
- frameId: 0,
- tabId: tabId,
- url: browser.currentURI.asciiSpec
- });
- }
-
- vAPI.setIcon(tabId, getOwnerWindow(target));
- };
-
- let locationChangedMessageName = location.host + ':locationChanged';
-
- let onLocationChanged = function (e) {
- let details = e.data;
-
- // Ignore notifications related to our popup
- if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) {
- return;
- }
-
- let browser = e.target;
- let tabId = tabIdFromTarget(browser);
- if (tabId === vAPI.noTabId) {
- return;
- }
-
- // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document"
- if (details.flags
- & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
- vAPI.tabs.onUpdated(tabId, {url: details.url}, {
- frameId: 0,
- tabId: tabId,
- url: browser.currentURI.asciiSpec
- });
- return;
- }
-
- // https://github.com/chrisaljoudi/uBlock/issues/105
- // Allow any kind of pages
- vAPI.tabs.onNavigation({
- frameId: 0,
- tabId: tabId,
- url: details.url
- });
- };
-
- let attachToTabBrowser = function (window) {
- if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') {
- vAPI.toolbarButton.attachToNewWindow(window);
- }
-
- let tabBrowser = getTabBrowser(window);
- if (tabBrowser === null) {
- return;
- }
-
- let tabContainer;
- if (tabBrowser.deck) {
- // Fennec
- tabContainer = tabBrowser.deck;
- } else if (tabBrowser.tabContainer) {
- // Firefox
- tabContainer = tabBrowser.tabContainer;
- vAPI.contextMenu.register(document);
- }
-
- // https://github.com/gorhill/uBlock/issues/697
- // Ignore `TabShow` events: unfortunately the `pending`
- // attribute is not set when a tab is opened as a result
- // of session restore -- it is set *after* the event is
- // fired in such case.
- if (tabContainer) {
- tabContainer.addEventListener('TabShow', onShow);
- tabContainer.addEventListener('TabClose', onClose);
- // when new window is opened TabSelect doesn't run on
- // the selected tab?
- tabContainer.addEventListener('TabSelect', onSelect);
- }
- };
-
- var canAttachToTabBrowser = function (window) {
- // https://github.com/gorhill/uBlock/issues/906
- // Ensure the environment is ready before trying to attaching.
- let document = window && window.document;
- if (!document || document.readyState !== 'complete') {
- return false;
- }
-
- // On some platforms, the tab browser isn't immediately
- // available, try waiting a bit if this
- // https://github.com/gorhill/uBlock/issues/763
- // Not getting a tab browser should not prevent from
- // attaching ourself to the window.
- let tabBrowser = getTabBrowser(window);
- if (tabBrowser === null) {
- return false;
- }
-
- return winWatcher.toBrowserWindow(window) !== null;
- };
-
- let onWindowLoad = function (win) {
- deferUntil(canAttachToTabBrowser.bind(null, win),
- attachToTabBrowser.bind(null, win));
- };
-
- let onWindowUnload = function (win) {
- vAPI.contextMenu.unregister(win.document);
-
- let tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- return;
- }
-
- let tabContainer = tabBrowser.tabContainer;
- if (tabContainer) {
- tabContainer.removeEventListener('TabShow', onShow);
- tabContainer.removeEventListener('TabClose', onClose);
- tabContainer.removeEventListener('TabSelect', onSelect);
- }
-
- // https://github.com/gorhill/uBlock/issues/574
- // To keep in mind: not all windows are tab containers,
- // sometimes the window IS the tab.
- let tabs;
- if (tabBrowser.tabs) {
- tabs = tabBrowser.tabs;
- } else if (tabBrowser.localName === 'browser') {
- tabs = [tabBrowser];
- } else {
- tabs = [];
- }
-
- let browser;
- let URI;
- let tabId;
- for (let i=tabs.length-1; i>=0; --i) {
- let tab = tabs[i];
- browser = browserFromTarget(tab);
- if (browser === null) {
- continue;
- }
-
- URI = browser.currentURI;
- // Close extension tabs
- if (URI.schemeIs('chrome') && URI.host === location.host) {
- removeInternal(tab, getTabBrowser(win));
- }
-
- tabId = browserToTabIdMap.get(browser);
- if (tabId !== undefined) {
- removeBrowserEntry(tabId, browser);
- tabIdToBrowserMap.delete(tabId);
- }
- browserToTabIdMap.delete(browser);
- }
- };
-
- var start = function () {
- // Initialize map with existing active tabs
- let tabBrowser;
- let tabs;
-
- for (let win of winWatcher.getWindows()) {
- onWindowLoad(win);
-
- tabBrowser = getTabBrowser(win);
- if (tabBrowser === null) {
- continue;
- }
-
- for (let tab of tabBrowser.tabs) {
- if (!tab.hasAttribute('pending')) {
- tabIdFromTarget(tab);
- }
- }
- }
-
- winWatcher.onOpenWindow = onWindowLoad;
- winWatcher.onCloseWindow = onWindowUnload;
-
- vAPI.messaging.globalMessageManager
- .addMessageListener(locationChangedMessageName,
- onLocationChanged);
- };
-
- let stop = function () {
- winWatcher.onOpenWindow = null;
- winWatcher.onCloseWindow = null;
-
- vAPI.messaging.globalMessageManager
- .removeMessageListener(locationChangedMessageName,
- onLocationChanged);
-
- for (let win of winWatcher.getWindows()) {
- onWindowUnload(win);
- }
-
- browserToTabIdMap = new WeakMap();
- tabIdToBrowserMap.clear();
- };
-
- cleanupTasks.push(stop);
-
- return {
- browsers: getAllBrowsers,
- browserFromTabId: browserFromTabId,
- browserFromTarget: browserFromTarget,
- currentBrowser: currentBrowser,
- indexFromTarget: indexFromTarget,
- removeTarget: removeTarget,
- start: start,
- tabFromBrowser: tabFromBrowser,
- tabIdFromTarget: tabIdFromTarget
- };
- })();
-
// Icon-related stuff
vAPI.setIcon = function (tabId, iconId, badge) {
// If badge is undefined, then setIcon was called from the
diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js
new file mode 100644
index 0000000..4c9caf5
--- /dev/null
+++ b/js/vapi-tabs.js
@@ -0,0 +1,735 @@
+/*******************************************************************************
+
+ ηMatrix - a browser extension to black/white list requests.
+ Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
+ Copyright (C) 2019 Alessio Vanni
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://gitlab.com/vannilla/ematrix
+ uMatrix Home: https://github.com/gorhill/uMatrix
+*/
+
+/* global self, Components */
+
+// For background page (tabs management)
+
+'use strict';
+
+/******************************************************************************/
+
+(function () {
+ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+ const {Services} = Cu.import('resource://gre/modules/Services.jsm', null);
+
+ let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js
+
+ vAPI.noTabId = '-1';
+
+ vAPI.tabs = {};
+
+ vAPI.tabs.registerListeners = function() {
+ tabWatcher.start();
+ };
+
+ vAPI.tabs.get = function (tabId, callback) {
+ // eMatrix: the following might be obsoleted (though probably
+ // still relevant at least for Pale Moon.)
+ //
+ // Firefox:
+ //
+ // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+
+ // ^ |
+ // | |
+ // +--------------------------------------------------------------+
+ //
+ // browser (browser)
+ // contentTitle
+ // currentURI
+ // ownerDocument (XULDocument)
+ // defaultView (ChromeWindow)
+ // gBrowser (tabbrowser OR browser)
+ // browsers (browser)
+ // selectedBrowser
+ // selectedTab
+ // tabs (tab.tabbrowser-tab)
+ //
+ // Fennec: (what I figured so far)
+ //
+ // tab -> browser windows -> window -> BrowserApp -> tabs --+
+ // ^ window |
+ // | |
+ // +-----------------------------------------------------------+
+ //
+ // tab
+ // browser
+ // [manual search to go back to tab from list of windows]
+ let browser;
+
+ if (tabId === null) {
+ browser = tabWatcher.currentBrowser();
+ tabId = tabWatcher.tabIdFromTarget(browser);
+ } else {
+ browser = tabWatcher.browserFromTabId(tabId);
+ }
+
+ // For internal use
+ if (typeof callback !== 'function') {
+ return browser;
+ }
+
+ if (!browser || !browser.currentURI) {
+ callback();
+ return;
+ }
+
+ let win = getOwnerWindow(browser);
+ let tabBrowser = getTabBrowser(win);
+
+ callback({
+ id: tabId,
+ windowId: winWatcher.idFromWindow(win),
+ active: tabBrowser !== null
+ && browser === tabBrowser.selectedBrowser,
+ url: browser.currentURI.asciiSpec,
+ title: browser.contentTitle
+ });
+ };
+
+ vAPI.tabs.getAllSync = function (window) {
+ let win;
+ let tabs = [];
+
+ for (let win of winWatcher.getWindows()) {
+ if (window && window !== win) {
+ continue;
+ }
+
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ continue;
+ }
+
+ // This can happens if a tab-less window is currently opened.
+ // Example of a tab-less window: one opened from clicking
+ // "View Page Source".
+ if (!tabBrowser.tabs) {
+ continue;
+ }
+
+ for (let tab of tabBrowser.tabs) {
+ tabs.push(tab);
+ }
+ }
+
+ return tabs;
+ };
+
+ vAPI.tabs.getAll = function (callback) {
+ let tabs = [];
+
+ for (let browser of tabWatcher.browsers()) {
+ let tab = tabWatcher.tabFromBrowser(browser);
+
+ if (tab === null) {
+ continue;
+ }
+
+ if (tab.hasAttribute('pending')) {
+ continue;
+ }
+
+ tabs.push({
+ id: tabWatcher.tabIdFromTarget(browser),
+ url: browser.currentURI.asciiSpec
+ });
+ }
+
+ callback(tabs);
+ };
+
+ vAPI.tabs.open = function (details) {
+ // properties of the details object:
+ // + url - the address that will be opened
+ // + tabId:- the tab is used if set, instead of creating a new one
+ // + index: - undefined: end of the list, -1: following tab, or
+ // after index
+ // + active: - opens the tab in background - true and undefined:
+ // foreground
+ // + select: - if a tab is already opened with that url, then
+ // select it instead of opening a new one
+ if (!details.url) {
+ return null;
+ }
+
+ // extension pages
+ if (/^[\w-]{2,}:/.test(details.url) === false) {
+ details.url = vAPI.getURL(details.url);
+ }
+
+ if (details.select) {
+ let URI = Services.io.newURI(details.url, null, null);
+
+ for (let tab of this.getAllSync()) {
+ let browser = tabWatcher.browserFromTarget(tab);
+
+ // https://github.com/gorhill/uBlock/issues/2558
+ if (browser === null) {
+ continue;
+ }
+
+ // Or simply .equals if we care about the fragment
+ if (URI.equalsExceptRef(browser.currentURI) === false) {
+ continue;
+ }
+
+ this.select(tab);
+
+ // Update URL if fragment is different
+ if (URI.equals(browser.currentURI) === false) {
+ browser.loadURI(URI.asciiSpec);
+ }
+
+ return;
+ }
+ }
+
+ if (details.active === undefined) {
+ details.active = true;
+ }
+
+ if (details.tabId) {
+ let tab = tabWatcher.browserFromTabId(details.tabId);
+
+ if (tab) {
+ tabWatcher.browserFromTarget(tab).loadURI(details.url);
+ return;
+ }
+ }
+
+ // Open in a standalone window
+ if (details.popup === true) {
+ Services.ww.openWindow(self,
+ details.url,
+ null,
+ 'location=1,menubar=1,personalbar=1,'
+ +'resizable=1,toolbar=1',
+ null);
+ return;
+ }
+
+ let win = winWatcher.getCurrentWindow();
+ let tabBrowser = getTabBrowser(win);
+
+ if (tabBrowser === null) {
+ return;
+ }
+
+ if (details.index === -1) {
+ details.index =
+ tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1;
+ }
+
+ let tab = tabBrowser.loadOneTab(details.url, {
+ inBackground: !details.active
+ });
+
+ if (details.index !== undefined) {
+ tabBrowser.moveTabTo(tab, details.index);
+ }
+ };
+
+ vAPI.tabs.replace = function (tabId, url) {
+ // Replace the URL of a tab. Noop if the tab does not exist.
+ let targetURL = url;
+
+ // extension pages
+ if (/^[\w-]{2,}:/.test(targetURL) !== true) {
+ targetURL = vAPI.getURL(targetURL);
+ }
+
+ let browser = tabWatcher.browserFromTabId(tabId);
+ if (browser) {
+ browser.loadURI(targetURL);
+ }
+ };
+
+ function removeInternal(tab, tabBrowser) {
+ if (tabBrowser) {
+ tabBrowser.removeTab(tab);
+ }
+ }
+
+ vAPI.tabs.remove = function (tabId) {
+ let browser = tabWatcher.browserFromTabId(tabId);
+ if (!browser) {
+ return;
+ }
+
+ let tab = tabWatcher.tabFromBrowser(browser);
+ if (!tab) {
+ return;
+ }
+
+ removeInternal(tab, getTabBrowser(getOwnerWindow(browser)));
+ };
+
+ vAPI.tabs.reload = function (tabId) {
+ let browser = tabWatcher.browserFromTabId(tabId);
+ if (!browser) {
+ return;
+ }
+
+ browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ };
+
+ vAPI.tabs.select = function (tab) {
+ if (typeof tab !== 'object') {
+ tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab));
+ }
+ if (!tab) {
+ return;
+ }
+
+ // https://github.com/gorhill/uBlock/issues/470
+ let win = getOwnerWindow(tab);
+ win.focus();
+
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser) {
+ tabBrowser.selectedTab = tab;
+ }
+ };
+
+ vAPI.tabs.injectScript = function (tabId, details, callback) {
+ let browser = tabWatcher.browserFromTabId(tabId);
+ if (!browser) {
+ return;
+ }
+
+ if (typeof details.file !== 'string') {
+ return;
+ }
+
+ details.file = vAPI.getURL(details.file);
+ browser.messageManager.sendAsyncMessage(location.host + ':broadcast',
+ JSON.stringify({
+ broadcast: true,
+ channelName: 'vAPI',
+ msg: {
+ cmd: 'injectScript',
+ details: details
+ }
+ }));
+
+ if (typeof callback === 'function') {
+ vAPI.setTimeout(callback, 13);
+ }
+ };
+
+ let tabWatcher = (function () {
+ // TODO: find out whether we need a janitor to take care of stale entries.
+
+ // https://github.com/gorhill/uMatrix/issues/540
+ // Use only weak references to hold onto browser references.
+ let browserToTabIdMap = new WeakMap();
+ let tabIdToBrowserMap = new Map();
+ let tabIdGenerator = 1;
+
+ let indexFromBrowser = function (browser) {
+ if (!browser) {
+ return -1;
+ }
+
+ let win = getOwnerWindow(browser);
+ if (!win) {
+ return -1;
+ }
+
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ return -1;
+ }
+
+ // This can happen, for example, the `view-source:`
+ // window, there is no tabbrowser object, the browser
+ // object sits directly in the window.
+ if (tabBrowser === browser) {
+ return 0;
+ }
+
+ return tabBrowser.browsers.indexOf(browser);
+ };
+
+ let indexFromTarget = function (target) {
+ return indexFromBrowser(browserFromTarget(target));
+ };
+
+ let tabFromBrowser = function (browser) {
+ let i = indexFromBrowser(browser);
+ if (i === -1) {
+ return null;
+ }
+
+ let win = getOwnerWindow(browser);
+ if (!win) {
+ return null;
+ }
+
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ return null;
+ }
+
+ if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) {
+ return null;
+ }
+
+ return tabBrowser.tabs[i];
+ };
+
+ let browserFromTarget = function (target) {
+ if (!target) {
+ return null;
+ }
+
+ if (target.linkedPanel) {
+ // target is a tab
+ target = target.linkedBrowser;
+ }
+
+ if (target.localName !== 'browser') {
+ return null;
+ }
+
+ return target;
+ };
+
+ let tabIdFromTarget = function (target) {
+ let browser = browserFromTarget(target);
+ if (browser === null) {
+ return vAPI.noTabId;
+ }
+
+ let tabId = browserToTabIdMap.get(browser);
+ if (tabId === undefined) {
+ tabId = '' + tabIdGenerator++;
+ browserToTabIdMap.set(browser, tabId);
+ tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser));
+ }
+
+ return tabId;
+ };
+
+ let browserFromTabId = function (tabId) {
+ let weakref = tabIdToBrowserMap.get(tabId);
+ let browser = weakref && weakref.get();
+
+ return browser || null;
+ };
+
+ let currentBrowser = function () {
+ let win = winWatcher.getCurrentWindow();
+
+ // https://github.com/gorhill/uBlock/issues/399
+ // getTabBrowser() can return null at browser launch time.
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ return null;
+ }
+
+ return browserFromTarget(tabBrowser.selectedTab);
+ };
+
+ let removeBrowserEntry = function (tabId, browser) {
+ if (tabId && tabId !== vAPI.noTabId) {
+ vAPI.tabs.onClosed(tabId);
+ delete vAPI.toolbarButton.tabs[tabId];
+ tabIdToBrowserMap.delete(tabId);
+ }
+
+ if (browser) {
+ browserToTabIdMap.delete(browser);
+ }
+ };
+
+ let removeTarget = function (target) {
+ onClose({
+ target: target
+ });
+ };
+
+ let getAllBrowsers = function () {
+ let browsers = [];
+
+ for (let [tabId, weakref] of tabIdToBrowserMap) {
+ let browser = weakref.get();
+
+ // TODO: Maybe call removeBrowserEntry() if the
+ // browser no longer exists?
+ if (browser) {
+ browsers.push(browser);
+ }
+ }
+
+ return browsers;
+ };
+
+ // var onOpen = function (target) {
+ // var tabId = tabIdFromTarget(target);
+ // var browser = browserFromTabId(tabId);
+ // vAPI.tabs.onNavigation({
+ // frameId: 0,
+ // tabId: tabId,
+ // url: browser.currentURI.asciiSpec,
+ // });
+ // };
+
+ var onShow = function ({target}) {
+ tabIdFromTarget(target);
+ };
+
+ var onClose = function ({target}) {
+ // target is tab in Firefox, browser in Fennec
+ let browser = browserFromTarget(target);
+ let tabId = browserToTabIdMap.get(browser);
+ removeBrowserEntry(tabId, browser);
+ };
+
+ var onSelect = function ({target}) {
+ // This is an entry point: when creating a new tab, it is
+ // not always reported through onLocationChanged...
+ // Sigh. It is "reported" here however.
+ let browser = browserFromTarget(target);
+ let tabId = browserToTabIdMap.get(browser);
+
+ if (tabId === undefined) {
+ tabId = tabIdFromTarget(target);
+ vAPI.tabs.onNavigation({
+ frameId: 0,
+ tabId: tabId,
+ url: browser.currentURI.asciiSpec
+ });
+ }
+
+ vAPI.setIcon(tabId, getOwnerWindow(target));
+ };
+
+ let locationChangedMessageName = location.host + ':locationChanged';
+
+ let onLocationChanged = function (e) {
+ let details = e.data;
+
+ // Ignore notifications related to our popup
+ if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) {
+ return;
+ }
+
+ let browser = e.target;
+ let tabId = tabIdFromTarget(browser);
+ if (tabId === vAPI.noTabId) {
+ return;
+ }
+
+ // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document"
+ if (details.flags
+ & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+ vAPI.tabs.onUpdated(tabId, {url: details.url}, {
+ frameId: 0,
+ tabId: tabId,
+ url: browser.currentURI.asciiSpec
+ });
+ return;
+ }
+
+ // https://github.com/chrisaljoudi/uBlock/issues/105
+ // Allow any kind of pages
+ vAPI.tabs.onNavigation({
+ frameId: 0,
+ tabId: tabId,
+ url: details.url
+ });
+ };
+
+ let attachToTabBrowser = function (window) {
+ if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') {
+ vAPI.toolbarButton.attachToNewWindow(window);
+ }
+
+ let tabBrowser = getTabBrowser(window);
+ if (tabBrowser === null) {
+ return;
+ }
+
+ let tabContainer;
+ if (tabBrowser.deck) {
+ // Fennec
+ tabContainer = tabBrowser.deck;
+ } else if (tabBrowser.tabContainer) {
+ // Firefox
+ tabContainer = tabBrowser.tabContainer;
+ vAPI.contextMenu.register(document);
+ }
+
+ // https://github.com/gorhill/uBlock/issues/697
+ // Ignore `TabShow` events: unfortunately the `pending`
+ // attribute is not set when a tab is opened as a result
+ // of session restore -- it is set *after* the event is
+ // fired in such case.
+ if (tabContainer) {
+ tabContainer.addEventListener('TabShow', onShow);
+ tabContainer.addEventListener('TabClose', onClose);
+ // when new window is opened TabSelect doesn't run on
+ // the selected tab?
+ tabContainer.addEventListener('TabSelect', onSelect);
+ }
+ };
+
+ var canAttachToTabBrowser = function (window) {
+ // https://github.com/gorhill/uBlock/issues/906
+ // Ensure the environment is ready before trying to attaching.
+ let document = window && window.document;
+ if (!document || document.readyState !== 'complete') {
+ return false;
+ }
+
+ // On some platforms, the tab browser isn't immediately
+ // available, try waiting a bit if this
+ // https://github.com/gorhill/uBlock/issues/763
+ // Not getting a tab browser should not prevent from
+ // attaching ourself to the window.
+ let tabBrowser = getTabBrowser(window);
+ if (tabBrowser === null) {
+ return false;
+ }
+
+ return winWatcher.toBrowserWindow(window) !== null;
+ };
+
+ let onWindowLoad = function (win) {
+ deferUntil(canAttachToTabBrowser.bind(null, win),
+ attachToTabBrowser.bind(null, win));
+ };
+
+ let onWindowUnload = function (win) {
+ vAPI.contextMenu.unregister(win.document);
+
+ let tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ return;
+ }
+
+ let tabContainer = tabBrowser.tabContainer;
+ if (tabContainer) {
+ tabContainer.removeEventListener('TabShow', onShow);
+ tabContainer.removeEventListener('TabClose', onClose);
+ tabContainer.removeEventListener('TabSelect', onSelect);
+ }
+
+ // https://github.com/gorhill/uBlock/issues/574
+ // To keep in mind: not all windows are tab containers,
+ // sometimes the window IS the tab.
+ let tabs;
+ if (tabBrowser.tabs) {
+ tabs = tabBrowser.tabs;
+ } else if (tabBrowser.localName === 'browser') {
+ tabs = [tabBrowser];
+ } else {
+ tabs = [];
+ }
+
+ let browser;
+ let URI;
+ let tabId;
+ for (let i=tabs.length-1; i>=0; --i) {
+ let tab = tabs[i];
+ browser = browserFromTarget(tab);
+ if (browser === null) {
+ continue;
+ }
+
+ URI = browser.currentURI;
+ // Close extension tabs
+ if (URI.schemeIs('chrome') && URI.host === location.host) {
+ removeInternal(tab, getTabBrowser(win));
+ }
+
+ tabId = browserToTabIdMap.get(browser);
+ if (tabId !== undefined) {
+ removeBrowserEntry(tabId, browser);
+ tabIdToBrowserMap.delete(tabId);
+ }
+ browserToTabIdMap.delete(browser);
+ }
+ };
+
+ var start = function () {
+ // Initialize map with existing active tabs
+ let tabBrowser;
+ let tabs;
+
+ for (let win of winWatcher.getWindows()) {
+ onWindowLoad(win);
+
+ tabBrowser = getTabBrowser(win);
+ if (tabBrowser === null) {
+ continue;
+ }
+
+ for (let tab of tabBrowser.tabs) {
+ if (!tab.hasAttribute('pending')) {
+ tabIdFromTarget(tab);
+ }
+ }
+ }
+
+ winWatcher.onOpenWindow = onWindowLoad;
+ winWatcher.onCloseWindow = onWindowUnload;
+
+ vAPI.messaging.globalMessageManager
+ .addMessageListener(locationChangedMessageName,
+ onLocationChanged);
+ };
+
+ let stop = function () {
+ winWatcher.onOpenWindow = null;
+ winWatcher.onCloseWindow = null;
+
+ vAPI.messaging.globalMessageManager
+ .removeMessageListener(locationChangedMessageName,
+ onLocationChanged);
+
+ for (let win of winWatcher.getWindows()) {
+ onWindowUnload(win);
+ }
+
+ browserToTabIdMap = new WeakMap();
+ tabIdToBrowserMap.clear();
+ };
+
+ vAPI.addCleanUpTask(stop);
+
+ return {
+ browsers: getAllBrowsers,
+ browserFromTabId: browserFromTabId,
+ browserFromTarget: browserFromTarget,
+ currentBrowser: currentBrowser,
+ indexFromTarget: indexFromTarget,
+ removeTarget: removeTarget,
+ start: start,
+ tabFromBrowser: tabFromBrowser,
+ tabIdFromTarget: tabIdFromTarget
+ };
+ })();
+})();