diff options
Diffstat (limited to '')
-rw-r--r-- | browser/modules/BrowserWindowTracker.jsm | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/browser/modules/BrowserWindowTracker.jsm b/browser/modules/BrowserWindowTracker.jsm new file mode 100644 index 0000000000..375600e135 --- /dev/null +++ b/browser/modules/BrowserWindowTracker.jsm @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This module tracks each browser window and informs network module + * the current selected tab's content outer window ID. + */ + +var EXPORTED_SYMBOLS = ["BrowserWindowTracker"]; + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +// Lazy getters +XPCOMUtils.defineLazyModuleGetters(this, { + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", +}); + +// Constants +const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"]; +const WINDOW_EVENTS = ["activate", "unload"]; +const DEBUG = false; + +// Variables +var _lastTopLevelWindowID = 0; +var _trackedWindows = []; + +// Global methods +function debug(s) { + if (DEBUG) { + dump("-*- UpdateTopLevelContentWindowIDHelper: " + s + "\n"); + } +} + +function _updateCurrentContentOuterWindowID(browser) { + if ( + !browser.outerWindowID || + browser.outerWindowID === _lastTopLevelWindowID || + browser.ownerGlobal != _trackedWindows[0] + ) { + return; + } + + debug( + "Current window uri=" + + (browser.currentURI && browser.currentURI.spec) + + " id=" + + browser.outerWindowID + ); + + _lastTopLevelWindowID = browser.outerWindowID; + let windowIDWrapper = Cc["@mozilla.org/supports-PRUint64;1"].createInstance( + Ci.nsISupportsPRUint64 + ); + windowIDWrapper.data = _lastTopLevelWindowID; + Services.obs.notifyObservers( + windowIDWrapper, + "net:current-toplevel-outer-content-windowid" + ); +} + +function _handleEvent(event) { + switch (event.type) { + case "TabBrowserInserted": + if ( + event.target.ownerGlobal.gBrowser.selectedBrowser === + event.target.linkedBrowser + ) { + _updateCurrentContentOuterWindowID(event.target.linkedBrowser); + } + break; + case "TabSelect": + _updateCurrentContentOuterWindowID(event.target.linkedBrowser); + break; + case "activate": + WindowHelper.onActivate(event.target); + break; + case "unload": + WindowHelper.removeWindow(event.currentTarget); + break; + } +} + +function _trackWindowOrder(window) { + if (window.windowState == window.STATE_MINIMIZED) { + let firstMinimizedWindow = _trackedWindows.findIndex( + w => w.windowState == w.STATE_MINIMIZED + ); + if (firstMinimizedWindow == -1) { + firstMinimizedWindow = _trackedWindows.length; + } + _trackedWindows.splice(firstMinimizedWindow, 0, window); + } else { + _trackedWindows.unshift(window); + } +} + +function _untrackWindowOrder(window) { + let idx = _trackedWindows.indexOf(window); + if (idx >= 0) { + _trackedWindows.splice(idx, 1); + } +} + +// Methods that impact a window. Put into single object for organization. +var WindowHelper = { + addWindow(window) { + // Add event listeners + TAB_EVENTS.forEach(function(event) { + window.gBrowser.tabContainer.addEventListener(event, _handleEvent); + }); + WINDOW_EVENTS.forEach(function(event) { + window.addEventListener(event, _handleEvent); + }); + + _trackWindowOrder(window); + + // Update the selected tab's content outer window ID. + _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser); + }, + + removeWindow(window) { + _untrackWindowOrder(window); + + // Remove the event listeners + TAB_EVENTS.forEach(function(event) { + window.gBrowser.tabContainer.removeEventListener(event, _handleEvent); + }); + WINDOW_EVENTS.forEach(function(event) { + window.removeEventListener(event, _handleEvent); + }); + }, + + onActivate(window) { + // If this window was the last focused window, we don't need to do anything + if (window == _trackedWindows[0]) { + return; + } + + _untrackWindowOrder(window); + _trackWindowOrder(window); + + _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser); + }, +}; + +this.BrowserWindowTracker = { + /** + * Get the most recent browser window. + * + * @param options an object accepting the arguments for the search. + * * private: true to restrict the search to private windows + * only, false to restrict the search to non-private only. + * Omit the property to search in both groups. + * * allowPopups: true if popup windows are permissable. + */ + getTopWindow(options = {}) { + for (let win of _trackedWindows) { + if ( + !win.closed && + (options.allowPopups || win.toolbar.visible) && + (!("private" in options) || + PrivateBrowsingUtils.permanentPrivateBrowsing || + PrivateBrowsingUtils.isWindowPrivate(win) == options.private) + ) { + return win; + } + } + return null; + }, + + windowCreated(browser) { + if (browser === browser.ownerGlobal.gBrowser.selectedBrowser) { + _updateCurrentContentOuterWindowID(browser); + } + }, + + /** + * Number of currently open browser windows. + */ + get windowCount() { + return _trackedWindows.length; + }, + + /** + * Array of browser windows ordered by z-index, in reverse order. + * This means that the top-most browser window will be the first item. + */ + get orderedWindows() { + // Clone the windows array immediately as it may change during iteration, + // we'd rather have an outdated order than skip/revisit windows. + return [..._trackedWindows]; + }, + + getAllVisibleTabs() { + let tabs = []; + for (let win of BrowserWindowTracker.orderedWindows) { + for (let tab of win.gBrowser.visibleTabs) { + // Only use tabs which are not discarded / unrestored + if (tab.linkedPanel) { + let { contentTitle, browserId } = tab.linkedBrowser; + tabs.push({ contentTitle, browserId }); + } + } + } + return tabs; + }, + + track(window) { + return WindowHelper.addWindow(window); + }, +}; |