1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/windows/tabs-firefox.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,216 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +module.metadata = { 1.10 + "stability": "unstable" 1.11 +}; 1.12 + 1.13 +const { Trait } = require("../deprecated/traits"); 1.14 +const { List } = require("../deprecated/list"); 1.15 +const { Tab } = require("../tabs/tab"); 1.16 +const { EventEmitter } = require("../deprecated/events"); 1.17 +const { EVENTS } = require("../tabs/events"); 1.18 +const { getOwnerWindow, getActiveTab, getTabs, 1.19 + openTab } = require("../tabs/utils"); 1.20 +const { Options } = require("../tabs/common"); 1.21 +const { observer: tabsObserver } = require("../tabs/observer"); 1.22 +const { ignoreWindow } = require("../private-browsing/utils"); 1.23 +const { when: unload } = require('../system/unload'); 1.24 + 1.25 +const TAB_BROWSER = "tabbrowser"; 1.26 + 1.27 +let unloaded = false; 1.28 +unload(_ => unloaded = true); 1.29 + 1.30 +/** 1.31 + * This is a trait that is used in composition of window wrapper. Trait tracks 1.32 + * tab related events of the wrapped window in order to keep track of open 1.33 + * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack 1.34 + * type event is emitted. 1.35 + */ 1.36 +const WindowTabTracker = Trait.compose({ 1.37 + /** 1.38 + * Chrome window whose tabs are tracked. 1.39 + */ 1.40 + _window: Trait.required, 1.41 + /** 1.42 + * Function used to emit events. 1.43 + */ 1.44 + _emit: EventEmitter.required, 1.45 + _tabOptions: Trait.required, 1.46 + /** 1.47 + * Function to add event listeners. 1.48 + */ 1.49 + on: EventEmitter.required, 1.50 + removeListener: EventEmitter.required, 1.51 + /** 1.52 + * Initializes tab tracker for a browser window. 1.53 + */ 1.54 + _initWindowTabTracker: function _initWindowTabTracker() { 1.55 + // Ugly hack that we have to remove at some point (see Bug 658059). At this 1.56 + // point it is necessary to invoke lazy `tabs` getter on the windows object 1.57 + // which creates a `TabList` instance. 1.58 + this.tabs; 1.59 + 1.60 + // Binding all methods used as event listeners to the instance. 1.61 + this._onTabReady = this._emitEvent.bind(this, "ready"); 1.62 + this._onTabLoad = this._emitEvent.bind(this, "load"); 1.63 + this._onTabPageShow = this._emitEvent.bind(this, "pageshow"); 1.64 + this._onTabOpen = this._onTabEvent.bind(this, "open"); 1.65 + this._onTabClose = this._onTabEvent.bind(this, "close"); 1.66 + this._onTabActivate = this._onTabEvent.bind(this, "activate"); 1.67 + this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate"); 1.68 + this._onTabPinned = this._onTabEvent.bind(this, "pinned"); 1.69 + this._onTabUnpinned = this._onTabEvent.bind(this, "unpinned"); 1.70 + 1.71 + for each (let tab in getTabs(this._window)) { 1.72 + // We emulate "open" events for all open tabs since gecko does not emits 1.73 + // them on the tabs that new windows are open with. Also this is 1.74 + // necessary to synchronize tabs lists with an actual state. 1.75 + this._onTabOpen(tab); 1.76 + } 1.77 + 1.78 + // We also emulate "activate" event so that it's picked up by a tab list. 1.79 + this._onTabActivate(getActiveTab(this._window)); 1.80 + 1.81 + // Setting up event listeners 1.82 + tabsObserver.on("open", this._onTabOpen); 1.83 + tabsObserver.on("close", this._onTabClose); 1.84 + tabsObserver.on("activate", this._onTabActivate); 1.85 + tabsObserver.on("deactivate", this._onTabDeactivate); 1.86 + tabsObserver.on("pinned", this._onTabPinned); 1.87 + tabsObserver.on("unpinned", this._onTabUnpinned); 1.88 + }, 1.89 + _destroyWindowTabTracker: function _destroyWindowTabTracker() { 1.90 + // We emulate close events on all tabs, since gecko does not emits such 1.91 + // events by itself. 1.92 + for each (let tab in this.tabs) 1.93 + this._emitEvent("close", tab); 1.94 + 1.95 + this._tabs._clear(); 1.96 + 1.97 + tabsObserver.removeListener("open", this._onTabOpen); 1.98 + tabsObserver.removeListener("close", this._onTabClose); 1.99 + tabsObserver.removeListener("activate", this._onTabActivate); 1.100 + tabsObserver.removeListener("deactivate", this._onTabDeactivate); 1.101 + }, 1.102 + _onTabEvent: function _onTabEvent(type, tab) { 1.103 + // Accept only tabs for the watched window, and ignore private tabs 1.104 + // if addon doesn't have private permission 1.105 + if (this._window === getOwnerWindow(tab) && !ignoreWindow(this._window)) { 1.106 + let options = this._tabOptions.shift() || {}; 1.107 + options.tab = tab; 1.108 + options.window = this._public; 1.109 + 1.110 + // Ignore zombie tabs on open that have already been removed 1.111 + if (type == "open" && !tab.linkedBrowser) 1.112 + return; 1.113 + 1.114 + // Create a tab wrapper on open event, otherwise, just fetch existing 1.115 + // tab object 1.116 + let wrappedTab = Tab(options, type !== "open"); 1.117 + if (!wrappedTab) 1.118 + return; 1.119 + 1.120 + // Setting up an event listener for ready events. 1.121 + if (type === "open") { 1.122 + wrappedTab.on("ready", this._onTabReady); 1.123 + wrappedTab.on("load", this._onTabLoad); 1.124 + wrappedTab.on("pageshow", this._onTabPageShow); 1.125 + } 1.126 + 1.127 + this._emitEvent(type, wrappedTab); 1.128 + } 1.129 + }, 1.130 + _emitEvent: function _emitEvent(type, tag) { 1.131 + if (unloaded) 1.132 + return; 1.133 + 1.134 + // Slices additional arguments and passes them into exposed 1.135 + // listener like other events (for pageshow) 1.136 + let args = Array.slice(arguments); 1.137 + // Notifies combined tab list that tab was added / removed. 1.138 + tabs._emit.apply(tabs, args); 1.139 + // Notifies contained tab list that window was added / removed. 1.140 + this._tabs._emit.apply(this._tabs, args); 1.141 + } 1.142 +}); 1.143 +exports.WindowTabTracker = WindowTabTracker; 1.144 + 1.145 +/** 1.146 + * This trait is used to create live representation of open tab lists. Each 1.147 + * window wrapper's tab list is represented by an object created from this 1.148 + * trait. It is also used to represent list of all the open windows. Trait is 1.149 + * composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events. 1.150 + * **Please note** that objects created by this trait can't be exposed outside 1.151 + * instead you should expose it's `_public` property, see comments in 1.152 + * constructor for details. 1.153 + */ 1.154 +const TabList = List.resolve({ constructor: "_init" }).compose( 1.155 + // This is ugly, but necessary. Will be removed by #596248 1.156 + EventEmitter.resolve({ toString: null }), 1.157 + Trait.compose({ 1.158 + on: Trait.required, 1.159 + _emit: Trait.required, 1.160 + constructor: function TabList(options) { 1.161 + this._window = options.window; 1.162 + // Add new items to the list 1.163 + this.on(EVENTS.open.name, this._add.bind(this)); 1.164 + 1.165 + // Remove closed items from the list 1.166 + this.on(EVENTS.close.name, this._remove.bind(this)); 1.167 + 1.168 + // Set value whenever new tab becomes active. 1.169 + this.on("activate", function onTabActivate(tab) { 1.170 + this._activeTab = tab; 1.171 + }.bind(this)); 1.172 + 1.173 + // Initialize list. 1.174 + this._init(); 1.175 + // This list is not going to emit any events, object holding this list 1.176 + // will do it instead, to make that possible we return a private API. 1.177 + return this; 1.178 + }, 1.179 + get activeTab() this._activeTab, 1.180 + _activeTab: null, 1.181 + 1.182 + open: function open(options) { 1.183 + let window = this._window; 1.184 + let chromeWindow = window._window; 1.185 + options = Options(options); 1.186 + 1.187 + // save the tab options 1.188 + window._tabOptions.push(options); 1.189 + // open the tab 1.190 + let tab = openTab(chromeWindow, options.url, options); 1.191 + } 1.192 + // This is ugly, but necessary. Will be removed by #596248 1.193 + }).resolve({ toString: null }) 1.194 +); 1.195 + 1.196 +/** 1.197 + * Combined list of all open tabs on all the windows. 1.198 + * type {TabList} 1.199 + */ 1.200 +var tabs = TabList({ window: null }); 1.201 +exports.tabs = tabs._public; 1.202 + 1.203 +/** 1.204 + * Trait is a part of composition that represents window wrapper. This trait is 1.205 + * composed out of `WindowTabTracker` that allows it to keep track of open tabs 1.206 + * on the window being wrapped. 1.207 + */ 1.208 +const WindowTabs = Trait.compose( 1.209 + WindowTabTracker, 1.210 + Trait.compose({ 1.211 + _window: Trait.required, 1.212 + /** 1.213 + * List of tabs 1.214 + */ 1.215 + get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public, 1.216 + _tabs: null, 1.217 + }) 1.218 +); 1.219 +exports.WindowTabs = WindowTabs;