|
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 { Cc, Ci, Cr } = require('chrome'), |
|
7 { Trait } = require('../deprecated/traits'), |
|
8 { List } = require('../deprecated/list'), |
|
9 { EventEmitter } = require('../deprecated/events'), |
|
10 { WindowTabs, WindowTabTracker } = require('./tabs-firefox'), |
|
11 { WindowDom } = require('./dom'), |
|
12 { WindowLoader } = require('./loader'), |
|
13 { isBrowser, getWindowDocShell, windows: windowIterator } = require('../window/utils'), |
|
14 { Options } = require('../tabs/common'), |
|
15 apiUtils = require('../deprecated/api-utils'), |
|
16 unload = require('../system/unload'), |
|
17 windowUtils = require('../deprecated/window-utils'), |
|
18 { WindowTrackerTrait } = windowUtils, |
|
19 { ns } = require('../core/namespace'), |
|
20 { observer: windowObserver } = require('./observer'), |
|
21 { getOwnerWindow } = require('../private-browsing/window/utils'); |
|
22 const { windowNS } = require('../window/namespace'); |
|
23 const { isPrivateBrowsingSupported } = require('../self'); |
|
24 const { ignoreWindow } = require('sdk/private-browsing/utils'); |
|
25 const { viewFor } = require('../view/core'); |
|
26 |
|
27 /** |
|
28 * Window trait composes safe wrappers for browser window that are E10S |
|
29 * compatible. |
|
30 */ |
|
31 const BrowserWindowTrait = Trait.compose( |
|
32 EventEmitter, |
|
33 WindowDom.resolve({ close: '_close' }), |
|
34 WindowTabs, |
|
35 WindowTabTracker, |
|
36 WindowLoader, |
|
37 /* WindowSidebars, */ |
|
38 Trait.compose({ |
|
39 _emit: Trait.required, |
|
40 _close: Trait.required, |
|
41 _load: Trait.required, |
|
42 /** |
|
43 * Constructor returns wrapper of the specified chrome window. |
|
44 * @param {nsIWindow} window |
|
45 */ |
|
46 constructor: function BrowserWindow(options) { |
|
47 // Register this window ASAP, in order to avoid loop that would try |
|
48 // to create this window instance over and over (see bug 648244) |
|
49 windows.push(this); |
|
50 |
|
51 // make sure we don't have unhandled errors |
|
52 this.on('error', console.exception.bind(console)); |
|
53 |
|
54 if ('onOpen' in options) |
|
55 this.on('open', options.onOpen); |
|
56 if ('onClose' in options) |
|
57 this.on('close', options.onClose); |
|
58 if ('onActivate' in options) |
|
59 this.on('activate', options.onActivate); |
|
60 if ('onDeactivate' in options) |
|
61 this.on('deactivate', options.onDeactivate); |
|
62 if ('window' in options) |
|
63 this._window = options.window; |
|
64 |
|
65 if ('tabs' in options) { |
|
66 this._tabOptions = Array.isArray(options.tabs) ? |
|
67 options.tabs.map(Options) : |
|
68 [ Options(options.tabs) ]; |
|
69 } |
|
70 else if ('url' in options) { |
|
71 this._tabOptions = [ Options(options.url) ]; |
|
72 } |
|
73 |
|
74 this._isPrivate = isPrivateBrowsingSupported && !!options.isPrivate; |
|
75 |
|
76 this._load(); |
|
77 |
|
78 windowNS(this._public).window = this._window; |
|
79 getOwnerWindow.implement(this._public, getChromeWindow); |
|
80 viewFor.implement(this._public, getChromeWindow); |
|
81 |
|
82 return this; |
|
83 }, |
|
84 destroy: function () this._onUnload(), |
|
85 _tabOptions: [], |
|
86 _onLoad: function() { |
|
87 try { |
|
88 this._initWindowTabTracker(); |
|
89 this._loaded = true; |
|
90 } |
|
91 catch(e) { |
|
92 this._emit('error', e); |
|
93 } |
|
94 |
|
95 this._emitOnObject(browserWindows, 'open', this._public); |
|
96 }, |
|
97 _onUnload: function() { |
|
98 if (!this._window) |
|
99 return; |
|
100 if (this._loaded) |
|
101 this._destroyWindowTabTracker(); |
|
102 |
|
103 this._emitOnObject(browserWindows, 'close', this._public); |
|
104 this._window = null; |
|
105 windowNS(this._public).window = null; |
|
106 // Removing reference from the windows array. |
|
107 windows.splice(windows.indexOf(this), 1); |
|
108 this._removeAllListeners(); |
|
109 }, |
|
110 close: function close(callback) { |
|
111 // maybe we should deprecate this with message ? |
|
112 if (callback) this.on('close', callback); |
|
113 return this._close(); |
|
114 } |
|
115 }) |
|
116 ); |
|
117 |
|
118 /** |
|
119 * Gets a `BrowserWindowTrait` for the given `chromeWindow` if previously |
|
120 * registered, `null` otherwise. |
|
121 */ |
|
122 function getRegisteredWindow(chromeWindow) { |
|
123 for each (let window in windows) { |
|
124 if (chromeWindow === window._window) |
|
125 return window; |
|
126 } |
|
127 |
|
128 return null; |
|
129 } |
|
130 |
|
131 /** |
|
132 * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for |
|
133 * window doesn't exists yet. If wrapper already exists then returns it |
|
134 * instead. |
|
135 * @params {Object} options |
|
136 * Options that are passed to the the `BrowserWindowTrait` |
|
137 * @returns {BrowserWindow} |
|
138 * @see BrowserWindowTrait |
|
139 */ |
|
140 function BrowserWindow(options) { |
|
141 let window = null; |
|
142 |
|
143 if ("window" in options) |
|
144 window = getRegisteredWindow(options.window); |
|
145 |
|
146 return (window || BrowserWindowTrait(options))._public; |
|
147 } |
|
148 // to have proper `instanceof` behavior will go away when #596248 is fixed. |
|
149 BrowserWindow.prototype = BrowserWindowTrait.prototype; |
|
150 exports.BrowserWindow = BrowserWindow; |
|
151 |
|
152 const windows = []; |
|
153 |
|
154 const browser = ns(); |
|
155 |
|
156 function onWindowActivation (chromeWindow, event) { |
|
157 if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window. |
|
158 |
|
159 let window = getRegisteredWindow(chromeWindow); |
|
160 |
|
161 if (window) |
|
162 window._emit(event.type, window._public); |
|
163 else |
|
164 window = BrowserWindowTrait({ window: chromeWindow }); |
|
165 |
|
166 browser(browserWindows).internals._emit(event.type, window._public); |
|
167 } |
|
168 |
|
169 windowObserver.on("activate", onWindowActivation); |
|
170 windowObserver.on("deactivate", onWindowActivation); |
|
171 |
|
172 /** |
|
173 * `BrowserWindows` trait is composed out of `List` trait and it represents |
|
174 * "live" list of currently open browser windows. Instance mutates itself |
|
175 * whenever new browser window gets opened / closed. |
|
176 */ |
|
177 // Very stupid to resolve all `toStrings` but this will be fixed by #596248 |
|
178 const browserWindows = Trait.resolve({ toString: null }).compose( |
|
179 List.resolve({ constructor: '_initList' }), |
|
180 EventEmitter.resolve({ toString: null }), |
|
181 WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }), |
|
182 Trait.compose({ |
|
183 _emit: Trait.required, |
|
184 _add: Trait.required, |
|
185 _remove: Trait.required, |
|
186 |
|
187 // public API |
|
188 |
|
189 /** |
|
190 * Constructor creates instance of `Windows` that represents live list of open |
|
191 * windows. |
|
192 */ |
|
193 constructor: function BrowserWindows() { |
|
194 browser(this._public).internals = this; |
|
195 |
|
196 this._trackedWindows = []; |
|
197 this._initList(); |
|
198 this._initTracker(); |
|
199 unload.ensure(this, "_destructor"); |
|
200 }, |
|
201 _destructor: function _destructor() { |
|
202 this._removeAllListeners('open'); |
|
203 this._removeAllListeners('close'); |
|
204 this._removeAllListeners('activate'); |
|
205 this._removeAllListeners('deactivate'); |
|
206 this._clear(); |
|
207 |
|
208 delete browser(this._public).internals; |
|
209 }, |
|
210 /** |
|
211 * This property represents currently active window. |
|
212 * Property is non-enumerable, in order to preserve array like enumeration. |
|
213 * @type {Window|null} |
|
214 */ |
|
215 get activeWindow() { |
|
216 let window = windowUtils.activeBrowserWindow; |
|
217 // Bug 834961: ignore private windows when they are not supported |
|
218 if (ignoreWindow(window)) |
|
219 window = windowIterator()[0]; |
|
220 return window ? BrowserWindow({window: window}) : null; |
|
221 }, |
|
222 open: function open(options) { |
|
223 if (typeof options === "string") { |
|
224 // `tabs` option is under review and may be removed. |
|
225 options = { |
|
226 tabs: [Options(options)], |
|
227 isPrivate: isPrivateBrowsingSupported && options.isPrivate |
|
228 }; |
|
229 } |
|
230 return BrowserWindow(options); |
|
231 }, |
|
232 |
|
233 /** |
|
234 * Internal listener which is called whenever new window gets open. |
|
235 * Creates wrapper and adds to this list. |
|
236 * @param {nsIWindow} chromeWindow |
|
237 */ |
|
238 _onTrack: function _onTrack(chromeWindow) { |
|
239 if (!isBrowser(chromeWindow)) return; |
|
240 let window = BrowserWindow({ window: chromeWindow }); |
|
241 this._add(window); |
|
242 this._emit('open', window); |
|
243 }, |
|
244 |
|
245 /** |
|
246 * Internal listener which is called whenever window gets closed. |
|
247 * Cleans up references and removes wrapper from this list. |
|
248 * @param {nsIWindow} window |
|
249 */ |
|
250 _onUntrack: function _onUntrack(chromeWindow) { |
|
251 if (!isBrowser(chromeWindow)) return; |
|
252 let window = BrowserWindow({ window: chromeWindow }); |
|
253 this._remove(window); |
|
254 this._emit('close', window); |
|
255 |
|
256 // Bug 724404: do not leak this module and linked windows: |
|
257 // We have to do it on untrack and not only when `_onUnload` is called |
|
258 // when windows are closed, otherwise, we will leak on addon disabling. |
|
259 window.destroy(); |
|
260 } |
|
261 }).resolve({ toString: null }) |
|
262 )(); |
|
263 |
|
264 function getChromeWindow(window) { |
|
265 return windowNS(window).window; |
|
266 } |
|
267 |
|
268 exports.browserWindows = browserWindows; |