1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/tabs/tab-firefox.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,297 @@ 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 +const { Trait } = require("../deprecated/traits"); 1.10 +const { EventEmitter } = require("../deprecated/events"); 1.11 +const { defer } = require("../lang/functional"); 1.12 +const { has } = require("../util/array"); 1.13 +const { EVENTS } = require("./events"); 1.14 +const { getThumbnailURIForWindow } = require("../content/thumbnail"); 1.15 +const { getFaviconURIForLocation } = require("../io/data"); 1.16 +const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle, 1.17 + getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils'); 1.18 +const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); 1.19 +const viewNS = require('../core/namespace').ns(); 1.20 +const { deprecateUsage } = require('../util/deprecate'); 1.21 +const { getURL } = require('../url/utils'); 1.22 +const { viewFor } = require('../view/core'); 1.23 + 1.24 +// Array of the inner instances of all the wrapped tabs. 1.25 +const TABS = []; 1.26 + 1.27 +/** 1.28 + * Trait used to create tab wrappers. 1.29 + */ 1.30 +const TabTrait = Trait.compose(EventEmitter, { 1.31 + on: Trait.required, 1.32 + _emit: Trait.required, 1.33 + /** 1.34 + * Tab DOM element that is being wrapped. 1.35 + */ 1.36 + _tab: null, 1.37 + /** 1.38 + * Window wrapper whose tab this object represents. 1.39 + */ 1.40 + window: null, 1.41 + constructor: function Tab(options) { 1.42 + this._onReady = this._onReady.bind(this); 1.43 + this._onLoad = this._onLoad.bind(this); 1.44 + this._onPageShow = this._onPageShow.bind(this); 1.45 + this._tab = options.tab; 1.46 + // TODO: Remove this dependency 1.47 + let window = this.window = options.window || require('../windows').BrowserWindow({ window: getOwnerWindow(this._tab) }); 1.48 + 1.49 + // Setting event listener if was passed. 1.50 + for each (let type in EVENTS) { 1.51 + let listener = options[type.listener]; 1.52 + if (listener) { 1.53 + this.on(type.name, options[type.listener]); 1.54 + } 1.55 + // window spreads this event. 1.56 + if (!has(['ready', 'load', 'pageshow'], (type.name))) 1.57 + window.tabs.on(type.name, this._onEvent.bind(this, type.name)); 1.58 + } 1.59 + 1.60 + this.on(EVENTS.close.name, this.destroy.bind(this)); 1.61 + 1.62 + this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true); 1.63 + this._browser.addEventListener(EVENTS.load.dom, this._onLoad, true); 1.64 + this._browser.addEventListener(EVENTS.pageshow.dom, this._onPageShow, true); 1.65 + 1.66 + if (options.isPinned) 1.67 + this.pin(); 1.68 + 1.69 + viewNS(this._public).tab = this._tab; 1.70 + getPBOwnerWindow.implement(this._public, getChromeTab); 1.71 + viewFor.implement(this._public, getTabView); 1.72 + 1.73 + // Add tabs to getURL method 1.74 + getURL.implement(this._public, (function (obj) this._public.url).bind(this)); 1.75 + 1.76 + // Since we will have to identify tabs by a DOM elements facade function 1.77 + // is used as constructor that collects all the instances and makes sure 1.78 + // that they more then one wrapper is not created per tab. 1.79 + return this; 1.80 + }, 1.81 + destroy: function destroy() { 1.82 + this._removeAllListeners(); 1.83 + if (this._tab) { 1.84 + let browser = this._browser; 1.85 + // The tab may already be removed from DOM -or- not yet added 1.86 + if (browser) { 1.87 + browser.removeEventListener(EVENTS.ready.dom, this._onReady, true); 1.88 + browser.removeEventListener(EVENTS.load.dom, this._onLoad, true); 1.89 + browser.removeEventListener(EVENTS.pageshow.dom, this._onPageShow, true); 1.90 + } 1.91 + this._tab = null; 1.92 + TABS.splice(TABS.indexOf(this), 1); 1.93 + } 1.94 + }, 1.95 + 1.96 + /** 1.97 + * Internal listener that emits public event 'ready' when the page of this 1.98 + * tab is loaded, from DOMContentLoaded 1.99 + */ 1.100 + _onReady: function _onReady(event) { 1.101 + // IFrames events will bubble so we need to ignore those. 1.102 + if (event.target == this._contentDocument) 1.103 + this._emit(EVENTS.ready.name, this._public); 1.104 + }, 1.105 + 1.106 + /** 1.107 + * Internal listener that emits public event 'load' when the page of this 1.108 + * tab is loaded, for triggering on non-HTML content, bug #671305 1.109 + */ 1.110 + _onLoad: function _onLoad(event) { 1.111 + // IFrames events will bubble so we need to ignore those. 1.112 + if (event.target == this._contentDocument) { 1.113 + this._emit(EVENTS.load.name, this._public); 1.114 + } 1.115 + }, 1.116 + 1.117 + /** 1.118 + * Internal listener that emits public event 'pageshow' when the page of this 1.119 + * tab is loaded from cache, bug #671305 1.120 + */ 1.121 + _onPageShow: function _onPageShow(event) { 1.122 + // IFrames events will bubble so we need to ignore those. 1.123 + if (event.target == this._contentDocument) { 1.124 + this._emit(EVENTS.pageshow.name, this._public, event.persisted); 1.125 + } 1.126 + }, 1.127 + /** 1.128 + * Internal tab event router. Window will emit tab related events for all it's 1.129 + * tabs, this listener will propagate all the events for this tab to it's 1.130 + * listeners. 1.131 + */ 1.132 + _onEvent: function _onEvent(type, tab) { 1.133 + if (viewNS(tab).tab == this._tab) 1.134 + this._emit(type, tab); 1.135 + }, 1.136 + /** 1.137 + * Browser DOM element where page of this tab is currently loaded. 1.138 + */ 1.139 + get _browser() getBrowserForTab(this._tab), 1.140 + /** 1.141 + * Window DOM element containing this tab. 1.142 + */ 1.143 + get _window() getOwnerWindow(this._tab), 1.144 + /** 1.145 + * Document object of the page that is currently loaded in this tab. 1.146 + */ 1.147 + get _contentDocument() this._browser.contentDocument, 1.148 + /** 1.149 + * Window object of the page that is currently loaded in this tab. 1.150 + */ 1.151 + get _contentWindow() this._browser.contentWindow, 1.152 + 1.153 + /** 1.154 + * Unique id for the tab, actually maps to tab.linkedPanel but with some munging. 1.155 + */ 1.156 + get id() this._tab ? getTabId(this._tab) : undefined, 1.157 + 1.158 + /** 1.159 + * The title of the page currently loaded in the tab. 1.160 + * Changing this property changes an actual title. 1.161 + * @type {String} 1.162 + */ 1.163 + get title() this._tab ? getTabTitle(this._tab) : undefined, 1.164 + set title(title) this._tab && setTabTitle(this._tab, title), 1.165 + 1.166 + /** 1.167 + * Returns the MIME type that the document loaded in the tab is being 1.168 + * rendered as. 1.169 + * @type {String} 1.170 + */ 1.171 + get contentType() this._tab ? getTabContentType(this._tab) : undefined, 1.172 + 1.173 + /** 1.174 + * Location of the page currently loaded in this tab. 1.175 + * Changing this property will loads page under under the specified location. 1.176 + * @type {String} 1.177 + */ 1.178 + get url() this._tab ? getTabURL(this._tab) : undefined, 1.179 + set url(url) this._tab && setTabURL(this._tab, url), 1.180 + /** 1.181 + * URI of the favicon for the page currently loaded in this tab. 1.182 + * @type {String} 1.183 + */ 1.184 + get favicon() { 1.185 + deprecateUsage( 1.186 + 'tab.favicon is deprecated, ' + 1.187 + 'please use require("sdk/places/favicon").getFavicon instead.' 1.188 + ); 1.189 + return this._tab ? getFaviconURIForLocation(this.url) : undefined 1.190 + }, 1.191 + /** 1.192 + * The CSS style for the tab 1.193 + */ 1.194 + get style() null, // TODO 1.195 + /** 1.196 + * The index of the tab relative to other tabs in the application window. 1.197 + * Changing this property will change order of the actual position of the tab. 1.198 + * @type {Number} 1.199 + */ 1.200 + get index() 1.201 + this._tab ? 1.202 + this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument) : 1.203 + undefined, 1.204 + set index(value) 1.205 + this._tab && this._window.gBrowser.moveTabTo(this._tab, value), 1.206 + /** 1.207 + * Thumbnail data URI of the page currently loaded in this tab. 1.208 + * @type {String} 1.209 + */ 1.210 + getThumbnail: function getThumbnail() 1.211 + this._tab ? getThumbnailURIForWindow(this._contentWindow) : undefined, 1.212 + /** 1.213 + * Whether or not tab is pinned (Is an app-tab). 1.214 + * @type {Boolean} 1.215 + */ 1.216 + get isPinned() this._tab ? this._tab.pinned : undefined, 1.217 + pin: function pin() { 1.218 + if (!this._tab) 1.219 + return; 1.220 + this._window.gBrowser.pinTab(this._tab); 1.221 + }, 1.222 + unpin: function unpin() { 1.223 + if (!this._tab) 1.224 + return; 1.225 + this._window.gBrowser.unpinTab(this._tab); 1.226 + }, 1.227 + 1.228 + /** 1.229 + * Create a worker for this tab, first argument is options given to Worker. 1.230 + * @type {Worker} 1.231 + */ 1.232 + attach: function attach(options) { 1.233 + if (!this._tab) 1.234 + return; 1.235 + // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946 1.236 + // TODO: fix this circular dependency 1.237 + let { Worker } = require('./worker'); 1.238 + return Worker(options, this._contentWindow); 1.239 + }, 1.240 + 1.241 + /** 1.242 + * Make this tab active. 1.243 + * Please note: That this function is called asynchronous since in E10S that 1.244 + * will be the case. Besides this function is called from a constructor where 1.245 + * we would like to return instance before firing a 'TabActivated' event. 1.246 + */ 1.247 + activate: defer(function activate() { 1.248 + if (!this._tab) 1.249 + return; 1.250 + activateTab(this._tab); 1.251 + }), 1.252 + /** 1.253 + * Close the tab 1.254 + */ 1.255 + close: function close(callback) { 1.256 + // Bug 699450: the tab may already have been detached 1.257 + if (!this._tab || !this._tab.parentNode) { 1.258 + if (callback) 1.259 + callback(); 1.260 + return; 1.261 + } 1.262 + if (callback) 1.263 + this.once(EVENTS.close.name, callback); 1.264 + this._window.gBrowser.removeTab(this._tab); 1.265 + }, 1.266 + /** 1.267 + * Reload the tab 1.268 + */ 1.269 + reload: function reload() { 1.270 + if (!this._tab) 1.271 + return; 1.272 + this._window.gBrowser.reloadTab(this._tab); 1.273 + } 1.274 +}); 1.275 + 1.276 +function getChromeTab(tab) { 1.277 + return getOwnerWindow(viewNS(tab).tab); 1.278 +} 1.279 + 1.280 +// Implement `viewFor` polymorphic function for the Tab 1.281 +// instances. 1.282 +const getTabView = tab => viewNS(tab).tab; 1.283 + 1.284 +function Tab(options, existingOnly) { 1.285 + let chromeTab = options.tab; 1.286 + for each (let tab in TABS) { 1.287 + if (chromeTab == tab._tab) 1.288 + return tab._public; 1.289 + } 1.290 + // If called asked to return only existing wrapper, 1.291 + // we should return null here as no matching Tab object has been found 1.292 + if (existingOnly) 1.293 + return null; 1.294 + 1.295 + let tab = TabTrait(options); 1.296 + TABS.push(tab); 1.297 + return tab._public; 1.298 +} 1.299 +Tab.prototype = TabTrait.prototype; 1.300 +exports.Tab = Tab;