michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: const { Trait } = require("../deprecated/traits"); michael@0: const { List } = require("../deprecated/list"); michael@0: const { Tab } = require("../tabs/tab"); michael@0: const { EventEmitter } = require("../deprecated/events"); michael@0: const { EVENTS } = require("../tabs/events"); michael@0: const { getOwnerWindow, getActiveTab, getTabs, michael@0: openTab } = require("../tabs/utils"); michael@0: const { Options } = require("../tabs/common"); michael@0: const { observer: tabsObserver } = require("../tabs/observer"); michael@0: const { ignoreWindow } = require("../private-browsing/utils"); michael@0: const { when: unload } = require('../system/unload'); michael@0: michael@0: const TAB_BROWSER = "tabbrowser"; michael@0: michael@0: let unloaded = false; michael@0: unload(_ => unloaded = true); michael@0: michael@0: /** michael@0: * This is a trait that is used in composition of window wrapper. Trait tracks michael@0: * tab related events of the wrapped window in order to keep track of open michael@0: * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack michael@0: * type event is emitted. michael@0: */ michael@0: const WindowTabTracker = Trait.compose({ michael@0: /** michael@0: * Chrome window whose tabs are tracked. michael@0: */ michael@0: _window: Trait.required, michael@0: /** michael@0: * Function used to emit events. michael@0: */ michael@0: _emit: EventEmitter.required, michael@0: _tabOptions: Trait.required, michael@0: /** michael@0: * Function to add event listeners. michael@0: */ michael@0: on: EventEmitter.required, michael@0: removeListener: EventEmitter.required, michael@0: /** michael@0: * Initializes tab tracker for a browser window. michael@0: */ michael@0: _initWindowTabTracker: function _initWindowTabTracker() { michael@0: // Ugly hack that we have to remove at some point (see Bug 658059). At this michael@0: // point it is necessary to invoke lazy `tabs` getter on the windows object michael@0: // which creates a `TabList` instance. michael@0: this.tabs; michael@0: michael@0: // Binding all methods used as event listeners to the instance. michael@0: this._onTabReady = this._emitEvent.bind(this, "ready"); michael@0: this._onTabLoad = this._emitEvent.bind(this, "load"); michael@0: this._onTabPageShow = this._emitEvent.bind(this, "pageshow"); michael@0: this._onTabOpen = this._onTabEvent.bind(this, "open"); michael@0: this._onTabClose = this._onTabEvent.bind(this, "close"); michael@0: this._onTabActivate = this._onTabEvent.bind(this, "activate"); michael@0: this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate"); michael@0: this._onTabPinned = this._onTabEvent.bind(this, "pinned"); michael@0: this._onTabUnpinned = this._onTabEvent.bind(this, "unpinned"); michael@0: michael@0: for each (let tab in getTabs(this._window)) { michael@0: // We emulate "open" events for all open tabs since gecko does not emits michael@0: // them on the tabs that new windows are open with. Also this is michael@0: // necessary to synchronize tabs lists with an actual state. michael@0: this._onTabOpen(tab); michael@0: } michael@0: michael@0: // We also emulate "activate" event so that it's picked up by a tab list. michael@0: this._onTabActivate(getActiveTab(this._window)); michael@0: michael@0: // Setting up event listeners michael@0: tabsObserver.on("open", this._onTabOpen); michael@0: tabsObserver.on("close", this._onTabClose); michael@0: tabsObserver.on("activate", this._onTabActivate); michael@0: tabsObserver.on("deactivate", this._onTabDeactivate); michael@0: tabsObserver.on("pinned", this._onTabPinned); michael@0: tabsObserver.on("unpinned", this._onTabUnpinned); michael@0: }, michael@0: _destroyWindowTabTracker: function _destroyWindowTabTracker() { michael@0: // We emulate close events on all tabs, since gecko does not emits such michael@0: // events by itself. michael@0: for each (let tab in this.tabs) michael@0: this._emitEvent("close", tab); michael@0: michael@0: this._tabs._clear(); michael@0: michael@0: tabsObserver.removeListener("open", this._onTabOpen); michael@0: tabsObserver.removeListener("close", this._onTabClose); michael@0: tabsObserver.removeListener("activate", this._onTabActivate); michael@0: tabsObserver.removeListener("deactivate", this._onTabDeactivate); michael@0: }, michael@0: _onTabEvent: function _onTabEvent(type, tab) { michael@0: // Accept only tabs for the watched window, and ignore private tabs michael@0: // if addon doesn't have private permission michael@0: if (this._window === getOwnerWindow(tab) && !ignoreWindow(this._window)) { michael@0: let options = this._tabOptions.shift() || {}; michael@0: options.tab = tab; michael@0: options.window = this._public; michael@0: michael@0: // Ignore zombie tabs on open that have already been removed michael@0: if (type == "open" && !tab.linkedBrowser) michael@0: return; michael@0: michael@0: // Create a tab wrapper on open event, otherwise, just fetch existing michael@0: // tab object michael@0: let wrappedTab = Tab(options, type !== "open"); michael@0: if (!wrappedTab) michael@0: return; michael@0: michael@0: // Setting up an event listener for ready events. michael@0: if (type === "open") { michael@0: wrappedTab.on("ready", this._onTabReady); michael@0: wrappedTab.on("load", this._onTabLoad); michael@0: wrappedTab.on("pageshow", this._onTabPageShow); michael@0: } michael@0: michael@0: this._emitEvent(type, wrappedTab); michael@0: } michael@0: }, michael@0: _emitEvent: function _emitEvent(type, tag) { michael@0: if (unloaded) michael@0: return; michael@0: michael@0: // Slices additional arguments and passes them into exposed michael@0: // listener like other events (for pageshow) michael@0: let args = Array.slice(arguments); michael@0: // Notifies combined tab list that tab was added / removed. michael@0: tabs._emit.apply(tabs, args); michael@0: // Notifies contained tab list that window was added / removed. michael@0: this._tabs._emit.apply(this._tabs, args); michael@0: } michael@0: }); michael@0: exports.WindowTabTracker = WindowTabTracker; michael@0: michael@0: /** michael@0: * This trait is used to create live representation of open tab lists. Each michael@0: * window wrapper's tab list is represented by an object created from this michael@0: * trait. It is also used to represent list of all the open windows. Trait is michael@0: * composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events. michael@0: * **Please note** that objects created by this trait can't be exposed outside michael@0: * instead you should expose it's `_public` property, see comments in michael@0: * constructor for details. michael@0: */ michael@0: const TabList = List.resolve({ constructor: "_init" }).compose( michael@0: // This is ugly, but necessary. Will be removed by #596248 michael@0: EventEmitter.resolve({ toString: null }), michael@0: Trait.compose({ michael@0: on: Trait.required, michael@0: _emit: Trait.required, michael@0: constructor: function TabList(options) { michael@0: this._window = options.window; michael@0: // Add new items to the list michael@0: this.on(EVENTS.open.name, this._add.bind(this)); michael@0: michael@0: // Remove closed items from the list michael@0: this.on(EVENTS.close.name, this._remove.bind(this)); michael@0: michael@0: // Set value whenever new tab becomes active. michael@0: this.on("activate", function onTabActivate(tab) { michael@0: this._activeTab = tab; michael@0: }.bind(this)); michael@0: michael@0: // Initialize list. michael@0: this._init(); michael@0: // This list is not going to emit any events, object holding this list michael@0: // will do it instead, to make that possible we return a private API. michael@0: return this; michael@0: }, michael@0: get activeTab() this._activeTab, michael@0: _activeTab: null, michael@0: michael@0: open: function open(options) { michael@0: let window = this._window; michael@0: let chromeWindow = window._window; michael@0: options = Options(options); michael@0: michael@0: // save the tab options michael@0: window._tabOptions.push(options); michael@0: // open the tab michael@0: let tab = openTab(chromeWindow, options.url, options); michael@0: } michael@0: // This is ugly, but necessary. Will be removed by #596248 michael@0: }).resolve({ toString: null }) michael@0: ); michael@0: michael@0: /** michael@0: * Combined list of all open tabs on all the windows. michael@0: * type {TabList} michael@0: */ michael@0: var tabs = TabList({ window: null }); michael@0: exports.tabs = tabs._public; michael@0: michael@0: /** michael@0: * Trait is a part of composition that represents window wrapper. This trait is michael@0: * composed out of `WindowTabTracker` that allows it to keep track of open tabs michael@0: * on the window being wrapped. michael@0: */ michael@0: const WindowTabs = Trait.compose( michael@0: WindowTabTracker, michael@0: Trait.compose({ michael@0: _window: Trait.required, michael@0: /** michael@0: * List of tabs michael@0: */ michael@0: get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public, michael@0: _tabs: null, michael@0: }) michael@0: ); michael@0: exports.WindowTabs = WindowTabs;