Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /*This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | 'use strict'; |
michael@0 | 5 | |
michael@0 | 6 | const { Trait } = require("../deprecated/traits"); |
michael@0 | 7 | const { EventEmitter } = require("../deprecated/events"); |
michael@0 | 8 | const { defer } = require("../lang/functional"); |
michael@0 | 9 | const { has } = require("../util/array"); |
michael@0 | 10 | const { EVENTS } = require("./events"); |
michael@0 | 11 | const { getThumbnailURIForWindow } = require("../content/thumbnail"); |
michael@0 | 12 | const { getFaviconURIForLocation } = require("../io/data"); |
michael@0 | 13 | const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle, |
michael@0 | 14 | getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils'); |
michael@0 | 15 | const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); |
michael@0 | 16 | const viewNS = require('../core/namespace').ns(); |
michael@0 | 17 | const { deprecateUsage } = require('../util/deprecate'); |
michael@0 | 18 | const { getURL } = require('../url/utils'); |
michael@0 | 19 | const { viewFor } = require('../view/core'); |
michael@0 | 20 | |
michael@0 | 21 | // Array of the inner instances of all the wrapped tabs. |
michael@0 | 22 | const TABS = []; |
michael@0 | 23 | |
michael@0 | 24 | /** |
michael@0 | 25 | * Trait used to create tab wrappers. |
michael@0 | 26 | */ |
michael@0 | 27 | const TabTrait = Trait.compose(EventEmitter, { |
michael@0 | 28 | on: Trait.required, |
michael@0 | 29 | _emit: Trait.required, |
michael@0 | 30 | /** |
michael@0 | 31 | * Tab DOM element that is being wrapped. |
michael@0 | 32 | */ |
michael@0 | 33 | _tab: null, |
michael@0 | 34 | /** |
michael@0 | 35 | * Window wrapper whose tab this object represents. |
michael@0 | 36 | */ |
michael@0 | 37 | window: null, |
michael@0 | 38 | constructor: function Tab(options) { |
michael@0 | 39 | this._onReady = this._onReady.bind(this); |
michael@0 | 40 | this._onLoad = this._onLoad.bind(this); |
michael@0 | 41 | this._onPageShow = this._onPageShow.bind(this); |
michael@0 | 42 | this._tab = options.tab; |
michael@0 | 43 | // TODO: Remove this dependency |
michael@0 | 44 | let window = this.window = options.window || require('../windows').BrowserWindow({ window: getOwnerWindow(this._tab) }); |
michael@0 | 45 | |
michael@0 | 46 | // Setting event listener if was passed. |
michael@0 | 47 | for each (let type in EVENTS) { |
michael@0 | 48 | let listener = options[type.listener]; |
michael@0 | 49 | if (listener) { |
michael@0 | 50 | this.on(type.name, options[type.listener]); |
michael@0 | 51 | } |
michael@0 | 52 | // window spreads this event. |
michael@0 | 53 | if (!has(['ready', 'load', 'pageshow'], (type.name))) |
michael@0 | 54 | window.tabs.on(type.name, this._onEvent.bind(this, type.name)); |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | this.on(EVENTS.close.name, this.destroy.bind(this)); |
michael@0 | 58 | |
michael@0 | 59 | this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true); |
michael@0 | 60 | this._browser.addEventListener(EVENTS.load.dom, this._onLoad, true); |
michael@0 | 61 | this._browser.addEventListener(EVENTS.pageshow.dom, this._onPageShow, true); |
michael@0 | 62 | |
michael@0 | 63 | if (options.isPinned) |
michael@0 | 64 | this.pin(); |
michael@0 | 65 | |
michael@0 | 66 | viewNS(this._public).tab = this._tab; |
michael@0 | 67 | getPBOwnerWindow.implement(this._public, getChromeTab); |
michael@0 | 68 | viewFor.implement(this._public, getTabView); |
michael@0 | 69 | |
michael@0 | 70 | // Add tabs to getURL method |
michael@0 | 71 | getURL.implement(this._public, (function (obj) this._public.url).bind(this)); |
michael@0 | 72 | |
michael@0 | 73 | // Since we will have to identify tabs by a DOM elements facade function |
michael@0 | 74 | // is used as constructor that collects all the instances and makes sure |
michael@0 | 75 | // that they more then one wrapper is not created per tab. |
michael@0 | 76 | return this; |
michael@0 | 77 | }, |
michael@0 | 78 | destroy: function destroy() { |
michael@0 | 79 | this._removeAllListeners(); |
michael@0 | 80 | if (this._tab) { |
michael@0 | 81 | let browser = this._browser; |
michael@0 | 82 | // The tab may already be removed from DOM -or- not yet added |
michael@0 | 83 | if (browser) { |
michael@0 | 84 | browser.removeEventListener(EVENTS.ready.dom, this._onReady, true); |
michael@0 | 85 | browser.removeEventListener(EVENTS.load.dom, this._onLoad, true); |
michael@0 | 86 | browser.removeEventListener(EVENTS.pageshow.dom, this._onPageShow, true); |
michael@0 | 87 | } |
michael@0 | 88 | this._tab = null; |
michael@0 | 89 | TABS.splice(TABS.indexOf(this), 1); |
michael@0 | 90 | } |
michael@0 | 91 | }, |
michael@0 | 92 | |
michael@0 | 93 | /** |
michael@0 | 94 | * Internal listener that emits public event 'ready' when the page of this |
michael@0 | 95 | * tab is loaded, from DOMContentLoaded |
michael@0 | 96 | */ |
michael@0 | 97 | _onReady: function _onReady(event) { |
michael@0 | 98 | // IFrames events will bubble so we need to ignore those. |
michael@0 | 99 | if (event.target == this._contentDocument) |
michael@0 | 100 | this._emit(EVENTS.ready.name, this._public); |
michael@0 | 101 | }, |
michael@0 | 102 | |
michael@0 | 103 | /** |
michael@0 | 104 | * Internal listener that emits public event 'load' when the page of this |
michael@0 | 105 | * tab is loaded, for triggering on non-HTML content, bug #671305 |
michael@0 | 106 | */ |
michael@0 | 107 | _onLoad: function _onLoad(event) { |
michael@0 | 108 | // IFrames events will bubble so we need to ignore those. |
michael@0 | 109 | if (event.target == this._contentDocument) { |
michael@0 | 110 | this._emit(EVENTS.load.name, this._public); |
michael@0 | 111 | } |
michael@0 | 112 | }, |
michael@0 | 113 | |
michael@0 | 114 | /** |
michael@0 | 115 | * Internal listener that emits public event 'pageshow' when the page of this |
michael@0 | 116 | * tab is loaded from cache, bug #671305 |
michael@0 | 117 | */ |
michael@0 | 118 | _onPageShow: function _onPageShow(event) { |
michael@0 | 119 | // IFrames events will bubble so we need to ignore those. |
michael@0 | 120 | if (event.target == this._contentDocument) { |
michael@0 | 121 | this._emit(EVENTS.pageshow.name, this._public, event.persisted); |
michael@0 | 122 | } |
michael@0 | 123 | }, |
michael@0 | 124 | /** |
michael@0 | 125 | * Internal tab event router. Window will emit tab related events for all it's |
michael@0 | 126 | * tabs, this listener will propagate all the events for this tab to it's |
michael@0 | 127 | * listeners. |
michael@0 | 128 | */ |
michael@0 | 129 | _onEvent: function _onEvent(type, tab) { |
michael@0 | 130 | if (viewNS(tab).tab == this._tab) |
michael@0 | 131 | this._emit(type, tab); |
michael@0 | 132 | }, |
michael@0 | 133 | /** |
michael@0 | 134 | * Browser DOM element where page of this tab is currently loaded. |
michael@0 | 135 | */ |
michael@0 | 136 | get _browser() getBrowserForTab(this._tab), |
michael@0 | 137 | /** |
michael@0 | 138 | * Window DOM element containing this tab. |
michael@0 | 139 | */ |
michael@0 | 140 | get _window() getOwnerWindow(this._tab), |
michael@0 | 141 | /** |
michael@0 | 142 | * Document object of the page that is currently loaded in this tab. |
michael@0 | 143 | */ |
michael@0 | 144 | get _contentDocument() this._browser.contentDocument, |
michael@0 | 145 | /** |
michael@0 | 146 | * Window object of the page that is currently loaded in this tab. |
michael@0 | 147 | */ |
michael@0 | 148 | get _contentWindow() this._browser.contentWindow, |
michael@0 | 149 | |
michael@0 | 150 | /** |
michael@0 | 151 | * Unique id for the tab, actually maps to tab.linkedPanel but with some munging. |
michael@0 | 152 | */ |
michael@0 | 153 | get id() this._tab ? getTabId(this._tab) : undefined, |
michael@0 | 154 | |
michael@0 | 155 | /** |
michael@0 | 156 | * The title of the page currently loaded in the tab. |
michael@0 | 157 | * Changing this property changes an actual title. |
michael@0 | 158 | * @type {String} |
michael@0 | 159 | */ |
michael@0 | 160 | get title() this._tab ? getTabTitle(this._tab) : undefined, |
michael@0 | 161 | set title(title) this._tab && setTabTitle(this._tab, title), |
michael@0 | 162 | |
michael@0 | 163 | /** |
michael@0 | 164 | * Returns the MIME type that the document loaded in the tab is being |
michael@0 | 165 | * rendered as. |
michael@0 | 166 | * @type {String} |
michael@0 | 167 | */ |
michael@0 | 168 | get contentType() this._tab ? getTabContentType(this._tab) : undefined, |
michael@0 | 169 | |
michael@0 | 170 | /** |
michael@0 | 171 | * Location of the page currently loaded in this tab. |
michael@0 | 172 | * Changing this property will loads page under under the specified location. |
michael@0 | 173 | * @type {String} |
michael@0 | 174 | */ |
michael@0 | 175 | get url() this._tab ? getTabURL(this._tab) : undefined, |
michael@0 | 176 | set url(url) this._tab && setTabURL(this._tab, url), |
michael@0 | 177 | /** |
michael@0 | 178 | * URI of the favicon for the page currently loaded in this tab. |
michael@0 | 179 | * @type {String} |
michael@0 | 180 | */ |
michael@0 | 181 | get favicon() { |
michael@0 | 182 | deprecateUsage( |
michael@0 | 183 | 'tab.favicon is deprecated, ' + |
michael@0 | 184 | 'please use require("sdk/places/favicon").getFavicon instead.' |
michael@0 | 185 | ); |
michael@0 | 186 | return this._tab ? getFaviconURIForLocation(this.url) : undefined |
michael@0 | 187 | }, |
michael@0 | 188 | /** |
michael@0 | 189 | * The CSS style for the tab |
michael@0 | 190 | */ |
michael@0 | 191 | get style() null, // TODO |
michael@0 | 192 | /** |
michael@0 | 193 | * The index of the tab relative to other tabs in the application window. |
michael@0 | 194 | * Changing this property will change order of the actual position of the tab. |
michael@0 | 195 | * @type {Number} |
michael@0 | 196 | */ |
michael@0 | 197 | get index() |
michael@0 | 198 | this._tab ? |
michael@0 | 199 | this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument) : |
michael@0 | 200 | undefined, |
michael@0 | 201 | set index(value) |
michael@0 | 202 | this._tab && this._window.gBrowser.moveTabTo(this._tab, value), |
michael@0 | 203 | /** |
michael@0 | 204 | * Thumbnail data URI of the page currently loaded in this tab. |
michael@0 | 205 | * @type {String} |
michael@0 | 206 | */ |
michael@0 | 207 | getThumbnail: function getThumbnail() |
michael@0 | 208 | this._tab ? getThumbnailURIForWindow(this._contentWindow) : undefined, |
michael@0 | 209 | /** |
michael@0 | 210 | * Whether or not tab is pinned (Is an app-tab). |
michael@0 | 211 | * @type {Boolean} |
michael@0 | 212 | */ |
michael@0 | 213 | get isPinned() this._tab ? this._tab.pinned : undefined, |
michael@0 | 214 | pin: function pin() { |
michael@0 | 215 | if (!this._tab) |
michael@0 | 216 | return; |
michael@0 | 217 | this._window.gBrowser.pinTab(this._tab); |
michael@0 | 218 | }, |
michael@0 | 219 | unpin: function unpin() { |
michael@0 | 220 | if (!this._tab) |
michael@0 | 221 | return; |
michael@0 | 222 | this._window.gBrowser.unpinTab(this._tab); |
michael@0 | 223 | }, |
michael@0 | 224 | |
michael@0 | 225 | /** |
michael@0 | 226 | * Create a worker for this tab, first argument is options given to Worker. |
michael@0 | 227 | * @type {Worker} |
michael@0 | 228 | */ |
michael@0 | 229 | attach: function attach(options) { |
michael@0 | 230 | if (!this._tab) |
michael@0 | 231 | return; |
michael@0 | 232 | // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946 |
michael@0 | 233 | // TODO: fix this circular dependency |
michael@0 | 234 | let { Worker } = require('./worker'); |
michael@0 | 235 | return Worker(options, this._contentWindow); |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | /** |
michael@0 | 239 | * Make this tab active. |
michael@0 | 240 | * Please note: That this function is called asynchronous since in E10S that |
michael@0 | 241 | * will be the case. Besides this function is called from a constructor where |
michael@0 | 242 | * we would like to return instance before firing a 'TabActivated' event. |
michael@0 | 243 | */ |
michael@0 | 244 | activate: defer(function activate() { |
michael@0 | 245 | if (!this._tab) |
michael@0 | 246 | return; |
michael@0 | 247 | activateTab(this._tab); |
michael@0 | 248 | }), |
michael@0 | 249 | /** |
michael@0 | 250 | * Close the tab |
michael@0 | 251 | */ |
michael@0 | 252 | close: function close(callback) { |
michael@0 | 253 | // Bug 699450: the tab may already have been detached |
michael@0 | 254 | if (!this._tab || !this._tab.parentNode) { |
michael@0 | 255 | if (callback) |
michael@0 | 256 | callback(); |
michael@0 | 257 | return; |
michael@0 | 258 | } |
michael@0 | 259 | if (callback) |
michael@0 | 260 | this.once(EVENTS.close.name, callback); |
michael@0 | 261 | this._window.gBrowser.removeTab(this._tab); |
michael@0 | 262 | }, |
michael@0 | 263 | /** |
michael@0 | 264 | * Reload the tab |
michael@0 | 265 | */ |
michael@0 | 266 | reload: function reload() { |
michael@0 | 267 | if (!this._tab) |
michael@0 | 268 | return; |
michael@0 | 269 | this._window.gBrowser.reloadTab(this._tab); |
michael@0 | 270 | } |
michael@0 | 271 | }); |
michael@0 | 272 | |
michael@0 | 273 | function getChromeTab(tab) { |
michael@0 | 274 | return getOwnerWindow(viewNS(tab).tab); |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | // Implement `viewFor` polymorphic function for the Tab |
michael@0 | 278 | // instances. |
michael@0 | 279 | const getTabView = tab => viewNS(tab).tab; |
michael@0 | 280 | |
michael@0 | 281 | function Tab(options, existingOnly) { |
michael@0 | 282 | let chromeTab = options.tab; |
michael@0 | 283 | for each (let tab in TABS) { |
michael@0 | 284 | if (chromeTab == tab._tab) |
michael@0 | 285 | return tab._public; |
michael@0 | 286 | } |
michael@0 | 287 | // If called asked to return only existing wrapper, |
michael@0 | 288 | // we should return null here as no matching Tab object has been found |
michael@0 | 289 | if (existingOnly) |
michael@0 | 290 | return null; |
michael@0 | 291 | |
michael@0 | 292 | let tab = TabTrait(options); |
michael@0 | 293 | TABS.push(tab); |
michael@0 | 294 | return tab._public; |
michael@0 | 295 | } |
michael@0 | 296 | Tab.prototype = TabTrait.prototype; |
michael@0 | 297 | exports.Tab = Tab; |