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