diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/vapi-background.js | 712 | ||||
-rw-r--r-- | js/vapi-tabs.js | 735 |
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 + }; + })(); +})(); |