123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
- Copyright (C) 2019-2022 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
- */
- 'use strict';
- /******************************************************************************/
- (function () {
- vAPI.tabs = {};
- vAPI.tabs.registerListeners = function() {
- vAPI.tabs.manager.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 = vAPI.tabs.manager.currentBrowser();
- tabId = vAPI.tabs.manager.tabIdFromTarget(browser);
- } else {
- browser = vAPI.tabs.manager.browserFromTabId(tabId);
- }
- // For internal use
- if (typeof callback !== 'function') {
- return browser;
- }
- if (!browser || !browser.currentURI) {
- callback();
- return;
- }
- let win = vAPI.browser.getOwnerWindow(browser);
- let tabBrowser = vAPI.browser.getTabBrowser(win);
- callback({
- id: tabId,
- windowId: vAPI.window.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 vAPI.window.getWindows()) {
- if (window && window !== win) {
- continue;
- }
- let tabBrowser = vAPI.browser.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 vAPI.tabs.manager.browsers()) {
- let tab = vAPI.tabs.manager.tabFromBrowser(browser);
- if (tab === null) {
- continue;
- }
- if (tab.hasAttribute('pending')) {
- continue;
- }
- tabs.push({
- id: vAPI.tabs.manager.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 = vAPI.tabs.manager.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 = vAPI.tabs.manager.browserFromTabId(details.tabId);
- if (tab) {
- vAPI.tabs.manager.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 = vAPI.window.getCurrentWindow();
- let tabBrowser = vAPI.browser.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 = vAPI.tabs.manager.browserFromTabId(tabId);
- if (browser) {
- browser.loadURI(targetURL);
- }
- };
- function removeInternal(tab, tabBrowser) {
- if (tabBrowser) {
- tabBrowser.removeTab(tab);
- }
- }
- vAPI.tabs.remove = function (tabId) {
- let browser = vAPI.tabs.manager.browserFromTabId(tabId);
- if (!browser) {
- return;
- }
- let tab = vAPI.tabs.manager.tabFromBrowser(browser);
- if (!tab) {
- return;
- }
- removeInternal(tab,
- vAPI.browser.getTabBrowser
- (vAPI.browser.getOwnerWindow(browser)));
- };
- vAPI.tabs.reload = function (tabId) {
- let browser = vAPI.tabs.manager.browserFromTabId(tabId);
- if (!browser) {
- return;
- }
- browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
- };
- vAPI.tabs.select = function (tab) {
- if (typeof tab !== 'object') {
- tab = vAPI.tabs.manager
- .tabFromBrowser(vAPI.tabs.manager.browserFromTabId(tab));
- }
- if (!tab) {
- return;
- }
- // https://github.com/gorhill/uBlock/issues/470
- let win = vAPI.browser.getOwnerWindow(tab);
- win.focus();
- let tabBrowser = vAPI.browser.getTabBrowser(win);
- if (tabBrowser) {
- tabBrowser.selectedTab = tab;
- }
- };
- vAPI.tabs.injectScript = function (tabId, details, callback) {
- let browser = vAPI.tabs.manager.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);
- }
- };
- vAPI.tabs.manager = (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 = vAPI.browser.getOwnerWindow(browser);
- if (!win) {
- return -1;
- }
- let tabBrowser = vAPI.browser.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 = vAPI.browser.getOwnerWindow(browser);
- if (!win) {
- return null;
- }
- let tabBrowser = vAPI.browser.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 = vAPI.window.getCurrentWindow();
- // https://github.com/gorhill/uBlock/issues/399
- // getTabBrowser() can return null at browser launch time.
- let tabBrowser = vAPI.browser.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, vAPI.browser.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 = vAPI.browser.getTabBrowser(window);
- if (tabBrowser === null) {
- return;
- }
- let tabContainer;
- if (tabBrowser.deck) {
- // Fennec
- tabContainer = tabBrowser.deck;
- } else if (tabBrowser.tabContainer) {
- // Firefox
- tabContainer = tabBrowser.tabContainer;
- }
- // 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 = vAPI.browser.getTabBrowser(window);
- if (tabBrowser === null) {
- return false;
- }
- return vAPI.window.toBrowserWindow(window) !== null;
- };
- let onWindowLoad = function (win) {
- vAPI.deferUntil(canAttachToTabBrowser.bind(null, win),
- attachToTabBrowser.bind(null, win));
- };
- let onWindowUnload = function (win) {
- let tabBrowser = vAPI.browser.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, vAPI.browser.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 vAPI.window.getWindows()) {
- onWindowLoad(win);
- tabBrowser = vAPI.browser.getTabBrowser(win);
- if (tabBrowser === null) {
- continue;
- }
- for (let tab of tabBrowser.tabs) {
- if (!tab.hasAttribute('pending')) {
- tabIdFromTarget(tab);
- }
- }
- }
- vAPI.window.onOpenWindow = onWindowLoad;
- vAPI.window.onCloseWindow = onWindowUnload;
- vAPI.messaging.globalMessageManager
- .addMessageListener(locationChangedMessageName,
- onLocationChanged);
- };
- let stop = function () {
- vAPI.window.onOpenWindow = null;
- vAPI.window.onCloseWindow = null;
- vAPI.messaging.globalMessageManager
- .removeMessageListener(locationChangedMessageName,
- onLocationChanged);
- for (let win of vAPI.window.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
- };
- })();
- })();
|