|
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 module.metadata = { |
|
7 'stability': 'deprecated' |
|
8 }; |
|
9 |
|
10 const { Cc, Ci } = require('chrome'); |
|
11 const { EventEmitter } = require('../deprecated/events'); |
|
12 const { Trait } = require('../deprecated/traits'); |
|
13 const { when } = require('../system/unload'); |
|
14 const events = require('../system/events'); |
|
15 const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser, |
|
16 getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils'); |
|
17 const errors = require('../deprecated/errors'); |
|
18 const { deprecateFunction } = require('../util/deprecate'); |
|
19 const { ignoreWindow, isGlobalPBSupported } = require('sdk/private-browsing/utils'); |
|
20 const { isPrivateBrowsingSupported } = require('../self'); |
|
21 |
|
22 const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. |
|
23 getService(Ci.nsIWindowWatcher); |
|
24 const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. |
|
25 getService(Ci.nsIAppShellService); |
|
26 |
|
27 // Bug 834961: ignore private windows when they are not supported |
|
28 function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported }); |
|
29 |
|
30 /** |
|
31 * An iterator for XUL windows currently in the application. |
|
32 * |
|
33 * @return A generator that yields XUL windows exposing the |
|
34 * nsIDOMWindow interface. |
|
35 */ |
|
36 function windowIterator() { |
|
37 // Bug 752631: We only pass already loaded window in order to avoid |
|
38 // breaking XUL windows DOM. DOM is broken when some JS code try |
|
39 // to access DOM during "uninitialized" state of the related document. |
|
40 let list = getWindows().filter(isDocumentLoaded); |
|
41 for (let i = 0, l = list.length; i < l; i++) { |
|
42 yield list[i]; |
|
43 } |
|
44 }; |
|
45 exports.windowIterator = windowIterator; |
|
46 |
|
47 /** |
|
48 * An iterator for browser windows currently open in the application. |
|
49 * @returns {Function} |
|
50 * A generator that yields browser windows exposing the `nsIDOMWindow` |
|
51 * interface. |
|
52 */ |
|
53 function browserWindowIterator() { |
|
54 for each (let window in windowIterator()) { |
|
55 if (isBrowser(window)) |
|
56 yield window; |
|
57 } |
|
58 } |
|
59 exports.browserWindowIterator = browserWindowIterator; |
|
60 |
|
61 function WindowTracker(delegate) { |
|
62 if (!(this instanceof WindowTracker)) { |
|
63 return new WindowTracker(delegate); |
|
64 } |
|
65 |
|
66 this._delegate = delegate; |
|
67 this._loadingWindows = []; |
|
68 |
|
69 for each (let window in getWindows()) |
|
70 this._regWindow(window); |
|
71 windowWatcher.registerNotification(this); |
|
72 this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this); |
|
73 events.on('toplevel-window-ready', this._onToplevelWindowReady); |
|
74 |
|
75 require('../system/unload').ensure(this); |
|
76 |
|
77 return this; |
|
78 }; |
|
79 |
|
80 WindowTracker.prototype = { |
|
81 _regLoadingWindow: function _regLoadingWindow(window) { |
|
82 // Bug 834961: ignore private windows when they are not supported |
|
83 if (ignoreWindow(window)) |
|
84 return; |
|
85 |
|
86 this._loadingWindows.push(window); |
|
87 window.addEventListener('load', this, true); |
|
88 }, |
|
89 |
|
90 _unregLoadingWindow: function _unregLoadingWindow(window) { |
|
91 var index = this._loadingWindows.indexOf(window); |
|
92 |
|
93 if (index != -1) { |
|
94 this._loadingWindows.splice(index, 1); |
|
95 window.removeEventListener('load', this, true); |
|
96 } |
|
97 }, |
|
98 |
|
99 _regWindow: function _regWindow(window) { |
|
100 // Bug 834961: ignore private windows when they are not supported |
|
101 if (ignoreWindow(window)) |
|
102 return; |
|
103 |
|
104 if (window.document.readyState == 'complete') { |
|
105 this._unregLoadingWindow(window); |
|
106 this._delegate.onTrack(window); |
|
107 } else |
|
108 this._regLoadingWindow(window); |
|
109 }, |
|
110 |
|
111 _unregWindow: function _unregWindow(window) { |
|
112 if (window.document.readyState == 'complete') { |
|
113 if (this._delegate.onUntrack) |
|
114 this._delegate.onUntrack(window); |
|
115 } else { |
|
116 this._unregLoadingWindow(window); |
|
117 } |
|
118 }, |
|
119 |
|
120 unload: function unload() { |
|
121 windowWatcher.unregisterNotification(this); |
|
122 events.off('toplevel-window-ready', this._onToplevelWindowReady); |
|
123 for each (let window in getWindows()) |
|
124 this._unregWindow(window); |
|
125 }, |
|
126 |
|
127 handleEvent: errors.catchAndLog(function handleEvent(event) { |
|
128 if (event.type == 'load' && event.target) { |
|
129 var window = event.target.defaultView; |
|
130 if (window) |
|
131 this._regWindow(window); |
|
132 } |
|
133 }), |
|
134 |
|
135 _onToplevelWindowReady: function _onToplevelWindowReady({subject}) { |
|
136 let window = subject; |
|
137 // ignore private windows if they are not supported |
|
138 if (ignoreWindow(window)) |
|
139 return; |
|
140 this._regWindow(window); |
|
141 }, |
|
142 |
|
143 observe: errors.catchAndLog(function observe(subject, topic, data) { |
|
144 var window = subject.QueryInterface(Ci.nsIDOMWindow); |
|
145 // ignore private windows if they are not supported |
|
146 if (ignoreWindow(window)) |
|
147 return; |
|
148 if (topic == 'domwindowclosed') |
|
149 this._unregWindow(window); |
|
150 }) |
|
151 }; |
|
152 exports.WindowTracker = WindowTracker; |
|
153 |
|
154 const WindowTrackerTrait = Trait.compose({ |
|
155 _onTrack: Trait.required, |
|
156 _onUntrack: Trait.required, |
|
157 constructor: function WindowTrackerTrait() { |
|
158 WindowTracker({ |
|
159 onTrack: this._onTrack.bind(this), |
|
160 onUntrack: this._onUntrack.bind(this) |
|
161 }); |
|
162 } |
|
163 }); |
|
164 exports.WindowTrackerTrait = WindowTrackerTrait; |
|
165 |
|
166 var gDocsToClose = []; |
|
167 |
|
168 function onDocUnload(event) { |
|
169 var index = gDocsToClose.indexOf(event.target); |
|
170 if (index == -1) |
|
171 throw new Error('internal error: unloading document not found'); |
|
172 var document = gDocsToClose.splice(index, 1)[0]; |
|
173 // Just in case, let's remove the event listener too. |
|
174 document.defaultView.removeEventListener('unload', onDocUnload, false); |
|
175 } |
|
176 |
|
177 onDocUnload = require('./errors').catchAndLog(onDocUnload); |
|
178 |
|
179 exports.closeOnUnload = function closeOnUnload(window) { |
|
180 window.addEventListener('unload', onDocUnload, false); |
|
181 gDocsToClose.push(window.document); |
|
182 }; |
|
183 |
|
184 Object.defineProperties(exports, { |
|
185 activeWindow: { |
|
186 enumerable: true, |
|
187 get: function() { |
|
188 return getMostRecentWindow(null); |
|
189 }, |
|
190 set: function(window) { |
|
191 try { |
|
192 window.focus(); |
|
193 } catch (e) {} |
|
194 } |
|
195 }, |
|
196 activeBrowserWindow: { |
|
197 enumerable: true, |
|
198 get: getMostRecentBrowserWindow |
|
199 } |
|
200 }); |
|
201 |
|
202 |
|
203 /** |
|
204 * Returns the ID of the window's current inner window. |
|
205 */ |
|
206 exports.getInnerId = deprecateFunction(getInnerId, |
|
207 'require("window-utils").getInnerId is deprecated, ' + |
|
208 'please use require("sdk/window/utils").getInnerId instead' |
|
209 ); |
|
210 |
|
211 exports.getOuterId = deprecateFunction(getOuterId, |
|
212 'require("window-utils").getOuterId is deprecated, ' + |
|
213 'please use require("sdk/window/utils").getOuterId instead' |
|
214 ); |
|
215 |
|
216 exports.isBrowser = deprecateFunction(isBrowser, |
|
217 'require("window-utils").isBrowser is deprecated, ' + |
|
218 'please use require("sdk/window/utils").isBrowser instead' |
|
219 ); |
|
220 |
|
221 exports.hiddenWindow = appShellService.hiddenDOMWindow; |
|
222 |
|
223 when( |
|
224 function() { |
|
225 gDocsToClose.slice().forEach( |
|
226 function(doc) { doc.defaultView.close(); }); |
|
227 }); |