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