|
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': 'unstable' |
|
8 }; |
|
9 |
|
10 const { Cc, Ci } = require('chrome'); |
|
11 const array = require('../util/array'); |
|
12 const { defer } = require('sdk/core/promise'); |
|
13 |
|
14 const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. |
|
15 getService(Ci.nsIWindowWatcher); |
|
16 const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. |
|
17 getService(Ci.nsIAppShellService); |
|
18 const WM = Cc['@mozilla.org/appshell/window-mediator;1']. |
|
19 getService(Ci.nsIWindowMediator); |
|
20 const io = Cc['@mozilla.org/network/io-service;1']. |
|
21 getService(Ci.nsIIOService); |
|
22 const FM = Cc["@mozilla.org/focus-manager;1"]. |
|
23 getService(Ci.nsIFocusManager); |
|
24 |
|
25 const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; |
|
26 |
|
27 const BROWSER = 'navigator:browser', |
|
28 URI_BROWSER = 'chrome://browser/content/browser.xul', |
|
29 NAME = '_blank', |
|
30 FEATURES = 'chrome,all,dialog=no,non-private'; |
|
31 |
|
32 function isWindowPrivate(win) { |
|
33 if (!win) |
|
34 return false; |
|
35 |
|
36 // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available, |
|
37 // and the app is Firefox, then assume per-window private browsing is |
|
38 // enabled. |
|
39 try { |
|
40 return win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
41 .getInterface(Ci.nsIWebNavigation) |
|
42 .QueryInterface(Ci.nsILoadContext) |
|
43 .usePrivateBrowsing; |
|
44 } |
|
45 catch(e) {} |
|
46 |
|
47 // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw. |
|
48 try { |
|
49 return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing; |
|
50 } |
|
51 catch (e) {} |
|
52 |
|
53 return false; |
|
54 } |
|
55 exports.isWindowPrivate = isWindowPrivate; |
|
56 |
|
57 function getMostRecentBrowserWindow() { |
|
58 return getMostRecentWindow(BROWSER); |
|
59 } |
|
60 exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow; |
|
61 |
|
62 function getHiddenWindow() { |
|
63 return appShellService.hiddenDOMWindow; |
|
64 } |
|
65 exports.getHiddenWindow = getHiddenWindow; |
|
66 |
|
67 function getMostRecentWindow(type) { |
|
68 return WM.getMostRecentWindow(type); |
|
69 } |
|
70 exports.getMostRecentWindow = getMostRecentWindow; |
|
71 |
|
72 /** |
|
73 * Returns the ID of the window's current inner window. |
|
74 */ |
|
75 function getInnerId(window) { |
|
76 return window.QueryInterface(Ci.nsIInterfaceRequestor). |
|
77 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; |
|
78 }; |
|
79 exports.getInnerId = getInnerId; |
|
80 |
|
81 /** |
|
82 * Returns the ID of the window's outer window. |
|
83 */ |
|
84 function getOuterId(window) { |
|
85 return window.QueryInterface(Ci.nsIInterfaceRequestor). |
|
86 getInterface(Ci.nsIDOMWindowUtils).outerWindowID; |
|
87 }; |
|
88 exports.getOuterId = getOuterId; |
|
89 |
|
90 /** |
|
91 * Returns window by the outer window id. |
|
92 */ |
|
93 const getByOuterId = WM.getOuterWindowWithId; |
|
94 exports.getByOuterId = getByOuterId; |
|
95 |
|
96 const getByInnerId = WM.getCurrentInnerWindowWithId; |
|
97 exports.getByInnerId = getByInnerId; |
|
98 |
|
99 /** |
|
100 * Returns `nsIXULWindow` for the given `nsIDOMWindow`. |
|
101 */ |
|
102 function getXULWindow(window) { |
|
103 return window.QueryInterface(Ci.nsIInterfaceRequestor). |
|
104 getInterface(Ci.nsIWebNavigation). |
|
105 QueryInterface(Ci.nsIDocShellTreeItem). |
|
106 treeOwner.QueryInterface(Ci.nsIInterfaceRequestor). |
|
107 getInterface(Ci.nsIXULWindow); |
|
108 }; |
|
109 exports.getXULWindow = getXULWindow; |
|
110 |
|
111 function getDOMWindow(xulWindow) { |
|
112 return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor). |
|
113 getInterface(Ci.nsIDOMWindow); |
|
114 } |
|
115 exports.getDOMWindow = getDOMWindow; |
|
116 |
|
117 /** |
|
118 * Returns `nsIBaseWindow` for the given `nsIDOMWindow`. |
|
119 */ |
|
120 function getBaseWindow(window) { |
|
121 return window.QueryInterface(Ci.nsIInterfaceRequestor). |
|
122 getInterface(Ci.nsIWebNavigation). |
|
123 QueryInterface(Ci.nsIDocShell). |
|
124 QueryInterface(Ci.nsIDocShellTreeItem). |
|
125 treeOwner. |
|
126 QueryInterface(Ci.nsIBaseWindow); |
|
127 } |
|
128 exports.getBaseWindow = getBaseWindow; |
|
129 |
|
130 /** |
|
131 * Returns the `nsIDOMWindow` toplevel window for any child/inner window |
|
132 */ |
|
133 function getToplevelWindow(window) { |
|
134 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
135 .getInterface(Ci.nsIWebNavigation) |
|
136 .QueryInterface(Ci.nsIDocShellTreeItem) |
|
137 .rootTreeItem |
|
138 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
139 .getInterface(Ci.nsIDOMWindow); |
|
140 } |
|
141 exports.getToplevelWindow = getToplevelWindow; |
|
142 |
|
143 function getWindowDocShell(window) window.gBrowser.docShell; |
|
144 exports.getWindowDocShell = getWindowDocShell; |
|
145 |
|
146 function getWindowLoadingContext(window) { |
|
147 return getWindowDocShell(window). |
|
148 QueryInterface(Ci.nsILoadContext); |
|
149 } |
|
150 exports.getWindowLoadingContext = getWindowLoadingContext; |
|
151 |
|
152 const isTopLevel = window => window && getToplevelWindow(window) === window; |
|
153 exports.isTopLevel = isTopLevel; |
|
154 |
|
155 /** |
|
156 * Takes hash of options and serializes it to a features string that |
|
157 * can be used passed to `window.open`. For more details on features string see: |
|
158 * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features |
|
159 */ |
|
160 function serializeFeatures(options) { |
|
161 return Object.keys(options).reduce(function(result, name) { |
|
162 let value = options[name]; |
|
163 |
|
164 // the chrome and private features are special |
|
165 if ((name == 'private' || name == 'chrome')) |
|
166 return result + ((value === true) ? ',' + name : ''); |
|
167 |
|
168 return result + ',' + name + '=' + |
|
169 (value === true ? 'yes' : value === false ? 'no' : value); |
|
170 }, '').substr(1); |
|
171 } |
|
172 |
|
173 /** |
|
174 * Opens a top level window and returns it's `nsIDOMWindow` representation. |
|
175 * @params {String} uri |
|
176 * URI of the document to be loaded into window. |
|
177 * @params {nsIDOMWindow} options.parent |
|
178 * Used as parent for the created window. |
|
179 * @params {String} options.name |
|
180 * Optional name that is assigned to the window. |
|
181 * @params {Object} options.features |
|
182 * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`. |
|
183 */ |
|
184 function open(uri, options) { |
|
185 uri = uri || URI_BROWSER; |
|
186 options = options || {}; |
|
187 |
|
188 if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0) |
|
189 throw new Error('only chrome, resource and data uris are allowed'); |
|
190 |
|
191 let newWindow = windowWatcher. |
|
192 openWindow(options.parent || null, |
|
193 uri, |
|
194 options.name || null, |
|
195 options.features ? serializeFeatures(options.features) : null, |
|
196 options.args || null); |
|
197 |
|
198 return newWindow; |
|
199 } |
|
200 exports.open = open; |
|
201 |
|
202 function onFocus(window) { |
|
203 let { resolve, promise } = defer(); |
|
204 |
|
205 if (isFocused(window)) { |
|
206 resolve(window); |
|
207 } |
|
208 else { |
|
209 window.addEventListener("focus", function focusListener() { |
|
210 window.removeEventListener("focus", focusListener, true); |
|
211 resolve(window); |
|
212 }, true); |
|
213 } |
|
214 |
|
215 return promise; |
|
216 } |
|
217 exports.onFocus = onFocus; |
|
218 |
|
219 function isFocused(window) { |
|
220 const FM = Cc["@mozilla.org/focus-manager;1"]. |
|
221 getService(Ci.nsIFocusManager); |
|
222 |
|
223 let childTargetWindow = {}; |
|
224 FM.getFocusedElementForWindow(window, true, childTargetWindow); |
|
225 childTargetWindow = childTargetWindow.value; |
|
226 |
|
227 let focusedChildWindow = {}; |
|
228 if (FM.activeWindow) { |
|
229 FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow); |
|
230 focusedChildWindow = focusedChildWindow.value; |
|
231 } |
|
232 |
|
233 return (focusedChildWindow === childTargetWindow); |
|
234 } |
|
235 exports.isFocused = isFocused; |
|
236 |
|
237 /** |
|
238 * Opens a top level window and returns it's `nsIDOMWindow` representation. |
|
239 * Same as `open` but with more features |
|
240 * @param {Object} options |
|
241 * |
|
242 */ |
|
243 function openDialog(options) { |
|
244 options = options || {}; |
|
245 |
|
246 let features = options.features || FEATURES; |
|
247 let featureAry = features.toLowerCase().split(','); |
|
248 |
|
249 if (!!options.private) { |
|
250 // add private flag if private window is desired |
|
251 if (!array.has(featureAry, 'private')) { |
|
252 featureAry.push('private'); |
|
253 } |
|
254 |
|
255 // remove the non-private flag ig a private window is desired |
|
256 let nonPrivateIndex = featureAry.indexOf('non-private'); |
|
257 if (nonPrivateIndex >= 0) { |
|
258 featureAry.splice(nonPrivateIndex, 1); |
|
259 } |
|
260 |
|
261 features = featureAry.join(','); |
|
262 } |
|
263 |
|
264 let browser = getMostRecentBrowserWindow(); |
|
265 |
|
266 // if there is no browser then do nothing |
|
267 if (!browser) |
|
268 return undefined; |
|
269 |
|
270 let newWindow = browser.openDialog.apply( |
|
271 browser, |
|
272 array.flatten([ |
|
273 options.url || URI_BROWSER, |
|
274 options.name || NAME, |
|
275 features, |
|
276 options.args || null |
|
277 ]) |
|
278 ); |
|
279 |
|
280 return newWindow; |
|
281 } |
|
282 exports.openDialog = openDialog; |
|
283 |
|
284 /** |
|
285 * Returns an array of all currently opened windows. |
|
286 * Note that these windows may still be loading. |
|
287 */ |
|
288 function windows(type, options) { |
|
289 options = options || {}; |
|
290 let list = []; |
|
291 let winEnum = WM.getEnumerator(type); |
|
292 while (winEnum.hasMoreElements()) { |
|
293 let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow); |
|
294 // Only add non-private windows when pb permission isn't set, |
|
295 // unless an option forces the addition of them. |
|
296 if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) { |
|
297 list.push(window); |
|
298 } |
|
299 } |
|
300 return list; |
|
301 } |
|
302 exports.windows = windows; |
|
303 |
|
304 /** |
|
305 * Check if the given window is interactive. |
|
306 * i.e. if its "DOMContentLoaded" event has already been fired. |
|
307 * @params {nsIDOMWindow} window |
|
308 */ |
|
309 const isInteractive = window => |
|
310 window.document.readyState === "interactive" || |
|
311 isDocumentLoaded(window) || |
|
312 // XUL documents stays '"uninitialized"' until it's `readyState` becomes |
|
313 // `"complete"`. |
|
314 isXULDocumentWindow(window) && window.document.readyState === "interactive"; |
|
315 exports.isInteractive = isInteractive; |
|
316 |
|
317 const isXULDocumentWindow = ({document}) => |
|
318 document.documentElement && |
|
319 document.documentElement.namespaceURI === XUL_NS; |
|
320 |
|
321 /** |
|
322 * Check if the given window is completely loaded. |
|
323 * i.e. if its "load" event has already been fired and all possible DOM content |
|
324 * is done loading (the whole DOM document, images content, ...) |
|
325 * @params {nsIDOMWindow} window |
|
326 */ |
|
327 function isDocumentLoaded(window) { |
|
328 return window.document.readyState == "complete"; |
|
329 } |
|
330 exports.isDocumentLoaded = isDocumentLoaded; |
|
331 |
|
332 function isBrowser(window) { |
|
333 try { |
|
334 return window.document.documentElement.getAttribute("windowtype") === BROWSER; |
|
335 } |
|
336 catch (e) {} |
|
337 return false; |
|
338 }; |
|
339 exports.isBrowser = isBrowser; |
|
340 |
|
341 function getWindowTitle(window) { |
|
342 return window && window.document ? window.document.title : null; |
|
343 } |
|
344 exports.getWindowTitle = getWindowTitle; |
|
345 |
|
346 function isXULBrowser(window) { |
|
347 return !!(isBrowser(window) && window.XULBrowserWindow); |
|
348 } |
|
349 exports.isXULBrowser = isXULBrowser; |
|
350 |
|
351 /** |
|
352 * Returns the most recent focused window |
|
353 */ |
|
354 function getFocusedWindow() { |
|
355 let window = WM.getMostRecentWindow(BROWSER); |
|
356 |
|
357 return window ? window.document.commandDispatcher.focusedWindow : null; |
|
358 } |
|
359 exports.getFocusedWindow = getFocusedWindow; |
|
360 |
|
361 /** |
|
362 * Returns the focused browser window if any, or the most recent one. |
|
363 * Opening new window, updates most recent window, but focus window |
|
364 * changes later; so most recent window and focused window are not always |
|
365 * the same. |
|
366 */ |
|
367 function getFocusedBrowser() { |
|
368 let window = FM.activeWindow; |
|
369 return isBrowser(window) ? window : getMostRecentBrowserWindow() |
|
370 } |
|
371 exports.getFocusedBrowser = getFocusedBrowser; |
|
372 |
|
373 /** |
|
374 * Returns the focused element in the most recent focused window |
|
375 */ |
|
376 function getFocusedElement() { |
|
377 let window = WM.getMostRecentWindow(BROWSER); |
|
378 |
|
379 return window ? window.document.commandDispatcher.focusedElement : null; |
|
380 } |
|
381 exports.getFocusedElement = getFocusedElement; |
|
382 |
|
383 function getFrames(window) { |
|
384 return Array.slice(window.frames).reduce(function(frames, frame) { |
|
385 return frames.concat(frame, getFrames(frame)); |
|
386 }, []); |
|
387 } |
|
388 exports.getFrames = getFrames; |
|
389 |
|
390 function getScreenPixelsPerCSSPixel(window) { |
|
391 return window.QueryInterface(Ci.nsIInterfaceRequestor). |
|
392 getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel; |
|
393 } |
|
394 exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel; |
|
395 |
|
396 function getOwnerBrowserWindow(node) { |
|
397 /** |
|
398 Takes DOM node and returns browser window that contains it. |
|
399 **/ |
|
400 let window = getToplevelWindow(node.ownerDocument.defaultView); |
|
401 // If anchored window is browser then it's target browser window. |
|
402 return isBrowser(window) ? window : null; |
|
403 } |
|
404 exports.getOwnerBrowserWindow = getOwnerBrowserWindow; |
|
405 |
|
406 function getParentWindow(window) { |
|
407 try { |
|
408 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
409 .getInterface(Ci.nsIWebNavigation) |
|
410 .QueryInterface(Ci.nsIDocShellTreeItem).parent |
|
411 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
412 .getInterface(Ci.nsIDOMWindow); |
|
413 } |
|
414 catch (e) {} |
|
415 return null; |
|
416 } |
|
417 exports.getParentWindow = getParentWindow; |
|
418 |
|
419 |
|
420 function getParentFrame(window) { |
|
421 try { |
|
422 return window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
423 .getInterface(Ci.nsIWebNavigation) |
|
424 .QueryInterface(Ci.nsIDocShellTreeItem).parent |
|
425 .QueryInterface(Ci.nsIInterfaceRequestor) |
|
426 .getInterface(Ci.nsIDOMWindow); |
|
427 } |
|
428 catch (e) {} |
|
429 return null; |
|
430 } |
|
431 exports.getParentWindow = getParentWindow; |
|
432 |
|
433 // The element in which the window is embedded, or `null` |
|
434 // if the window is top-level. Similar to `window.frameElement` |
|
435 // but can cross chrome-content boundries. |
|
436 const getFrameElement = target => |
|
437 (target instanceof Ci.nsIDOMDocument ? target.defaultView : target). |
|
438 QueryInterface(Ci.nsIInterfaceRequestor). |
|
439 getInterface(Ci.nsIDOMWindowUtils). |
|
440 containerElement; |
|
441 exports.getFrameElement = getFrameElement; |