michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: 'stability': 'unstable' michael@0: }; michael@0: michael@0: const { Cc, Ci } = require('chrome'); michael@0: const array = require('../util/array'); michael@0: const { defer } = require('sdk/core/promise'); michael@0: michael@0: const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. michael@0: getService(Ci.nsIWindowWatcher); michael@0: const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. michael@0: getService(Ci.nsIAppShellService); michael@0: const WM = Cc['@mozilla.org/appshell/window-mediator;1']. michael@0: getService(Ci.nsIWindowMediator); michael@0: const io = Cc['@mozilla.org/network/io-service;1']. michael@0: getService(Ci.nsIIOService); michael@0: const FM = Cc["@mozilla.org/focus-manager;1"]. michael@0: getService(Ci.nsIFocusManager); michael@0: michael@0: const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; michael@0: michael@0: const BROWSER = 'navigator:browser', michael@0: URI_BROWSER = 'chrome://browser/content/browser.xul', michael@0: NAME = '_blank', michael@0: FEATURES = 'chrome,all,dialog=no,non-private'; michael@0: michael@0: function isWindowPrivate(win) { michael@0: if (!win) michael@0: return false; michael@0: michael@0: // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available, michael@0: // and the app is Firefox, then assume per-window private browsing is michael@0: // enabled. michael@0: try { michael@0: return win.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsILoadContext) michael@0: .usePrivateBrowsing; michael@0: } michael@0: catch(e) {} michael@0: michael@0: // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw. michael@0: try { michael@0: return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing; michael@0: } michael@0: catch (e) {} michael@0: michael@0: return false; michael@0: } michael@0: exports.isWindowPrivate = isWindowPrivate; michael@0: michael@0: function getMostRecentBrowserWindow() { michael@0: return getMostRecentWindow(BROWSER); michael@0: } michael@0: exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow; michael@0: michael@0: function getHiddenWindow() { michael@0: return appShellService.hiddenDOMWindow; michael@0: } michael@0: exports.getHiddenWindow = getHiddenWindow; michael@0: michael@0: function getMostRecentWindow(type) { michael@0: return WM.getMostRecentWindow(type); michael@0: } michael@0: exports.getMostRecentWindow = getMostRecentWindow; michael@0: michael@0: /** michael@0: * Returns the ID of the window's current inner window. michael@0: */ michael@0: function getInnerId(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; michael@0: }; michael@0: exports.getInnerId = getInnerId; michael@0: michael@0: /** michael@0: * Returns the ID of the window's outer window. michael@0: */ michael@0: function getOuterId(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: }; michael@0: exports.getOuterId = getOuterId; michael@0: michael@0: /** michael@0: * Returns window by the outer window id. michael@0: */ michael@0: const getByOuterId = WM.getOuterWindowWithId; michael@0: exports.getByOuterId = getByOuterId; michael@0: michael@0: const getByInnerId = WM.getCurrentInnerWindowWithId; michael@0: exports.getByInnerId = getByInnerId; michael@0: michael@0: /** michael@0: * Returns `nsIXULWindow` for the given `nsIDOMWindow`. michael@0: */ michael@0: function getXULWindow(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebNavigation). michael@0: QueryInterface(Ci.nsIDocShellTreeItem). michael@0: treeOwner.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIXULWindow); michael@0: }; michael@0: exports.getXULWindow = getXULWindow; michael@0: michael@0: function getDOMWindow(xulWindow) { michael@0: return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: exports.getDOMWindow = getDOMWindow; michael@0: michael@0: /** michael@0: * Returns `nsIBaseWindow` for the given `nsIDOMWindow`. michael@0: */ michael@0: function getBaseWindow(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebNavigation). michael@0: QueryInterface(Ci.nsIDocShell). michael@0: QueryInterface(Ci.nsIDocShellTreeItem). michael@0: treeOwner. michael@0: QueryInterface(Ci.nsIBaseWindow); michael@0: } michael@0: exports.getBaseWindow = getBaseWindow; michael@0: michael@0: /** michael@0: * Returns the `nsIDOMWindow` toplevel window for any child/inner window michael@0: */ michael@0: function getToplevelWindow(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: exports.getToplevelWindow = getToplevelWindow; michael@0: michael@0: function getWindowDocShell(window) window.gBrowser.docShell; michael@0: exports.getWindowDocShell = getWindowDocShell; michael@0: michael@0: function getWindowLoadingContext(window) { michael@0: return getWindowDocShell(window). michael@0: QueryInterface(Ci.nsILoadContext); michael@0: } michael@0: exports.getWindowLoadingContext = getWindowLoadingContext; michael@0: michael@0: const isTopLevel = window => window && getToplevelWindow(window) === window; michael@0: exports.isTopLevel = isTopLevel; michael@0: michael@0: /** michael@0: * Takes hash of options and serializes it to a features string that michael@0: * can be used passed to `window.open`. For more details on features string see: michael@0: * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features michael@0: */ michael@0: function serializeFeatures(options) { michael@0: return Object.keys(options).reduce(function(result, name) { michael@0: let value = options[name]; michael@0: michael@0: // the chrome and private features are special michael@0: if ((name == 'private' || name == 'chrome')) michael@0: return result + ((value === true) ? ',' + name : ''); michael@0: michael@0: return result + ',' + name + '=' + michael@0: (value === true ? 'yes' : value === false ? 'no' : value); michael@0: }, '').substr(1); michael@0: } michael@0: michael@0: /** michael@0: * Opens a top level window and returns it's `nsIDOMWindow` representation. michael@0: * @params {String} uri michael@0: * URI of the document to be loaded into window. michael@0: * @params {nsIDOMWindow} options.parent michael@0: * Used as parent for the created window. michael@0: * @params {String} options.name michael@0: * Optional name that is assigned to the window. michael@0: * @params {Object} options.features michael@0: * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`. michael@0: */ michael@0: function open(uri, options) { michael@0: uri = uri || URI_BROWSER; michael@0: options = options || {}; michael@0: michael@0: if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0) michael@0: throw new Error('only chrome, resource and data uris are allowed'); michael@0: michael@0: let newWindow = windowWatcher. michael@0: openWindow(options.parent || null, michael@0: uri, michael@0: options.name || null, michael@0: options.features ? serializeFeatures(options.features) : null, michael@0: options.args || null); michael@0: michael@0: return newWindow; michael@0: } michael@0: exports.open = open; michael@0: michael@0: function onFocus(window) { michael@0: let { resolve, promise } = defer(); michael@0: michael@0: if (isFocused(window)) { michael@0: resolve(window); michael@0: } michael@0: else { michael@0: window.addEventListener("focus", function focusListener() { michael@0: window.removeEventListener("focus", focusListener, true); michael@0: resolve(window); michael@0: }, true); michael@0: } michael@0: michael@0: return promise; michael@0: } michael@0: exports.onFocus = onFocus; michael@0: michael@0: function isFocused(window) { michael@0: const FM = Cc["@mozilla.org/focus-manager;1"]. michael@0: getService(Ci.nsIFocusManager); michael@0: michael@0: let childTargetWindow = {}; michael@0: FM.getFocusedElementForWindow(window, true, childTargetWindow); michael@0: childTargetWindow = childTargetWindow.value; michael@0: michael@0: let focusedChildWindow = {}; michael@0: if (FM.activeWindow) { michael@0: FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow); michael@0: focusedChildWindow = focusedChildWindow.value; michael@0: } michael@0: michael@0: return (focusedChildWindow === childTargetWindow); michael@0: } michael@0: exports.isFocused = isFocused; michael@0: michael@0: /** michael@0: * Opens a top level window and returns it's `nsIDOMWindow` representation. michael@0: * Same as `open` but with more features michael@0: * @param {Object} options michael@0: * michael@0: */ michael@0: function openDialog(options) { michael@0: options = options || {}; michael@0: michael@0: let features = options.features || FEATURES; michael@0: let featureAry = features.toLowerCase().split(','); michael@0: michael@0: if (!!options.private) { michael@0: // add private flag if private window is desired michael@0: if (!array.has(featureAry, 'private')) { michael@0: featureAry.push('private'); michael@0: } michael@0: michael@0: // remove the non-private flag ig a private window is desired michael@0: let nonPrivateIndex = featureAry.indexOf('non-private'); michael@0: if (nonPrivateIndex >= 0) { michael@0: featureAry.splice(nonPrivateIndex, 1); michael@0: } michael@0: michael@0: features = featureAry.join(','); michael@0: } michael@0: michael@0: let browser = getMostRecentBrowserWindow(); michael@0: michael@0: // if there is no browser then do nothing michael@0: if (!browser) michael@0: return undefined; michael@0: michael@0: let newWindow = browser.openDialog.apply( michael@0: browser, michael@0: array.flatten([ michael@0: options.url || URI_BROWSER, michael@0: options.name || NAME, michael@0: features, michael@0: options.args || null michael@0: ]) michael@0: ); michael@0: michael@0: return newWindow; michael@0: } michael@0: exports.openDialog = openDialog; michael@0: michael@0: /** michael@0: * Returns an array of all currently opened windows. michael@0: * Note that these windows may still be loading. michael@0: */ michael@0: function windows(type, options) { michael@0: options = options || {}; michael@0: let list = []; michael@0: let winEnum = WM.getEnumerator(type); michael@0: while (winEnum.hasMoreElements()) { michael@0: let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow); michael@0: // Only add non-private windows when pb permission isn't set, michael@0: // unless an option forces the addition of them. michael@0: if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) { michael@0: list.push(window); michael@0: } michael@0: } michael@0: return list; michael@0: } michael@0: exports.windows = windows; michael@0: michael@0: /** michael@0: * Check if the given window is interactive. michael@0: * i.e. if its "DOMContentLoaded" event has already been fired. michael@0: * @params {nsIDOMWindow} window michael@0: */ michael@0: const isInteractive = window => michael@0: window.document.readyState === "interactive" || michael@0: isDocumentLoaded(window) || michael@0: // XUL documents stays '"uninitialized"' until it's `readyState` becomes michael@0: // `"complete"`. michael@0: isXULDocumentWindow(window) && window.document.readyState === "interactive"; michael@0: exports.isInteractive = isInteractive; michael@0: michael@0: const isXULDocumentWindow = ({document}) => michael@0: document.documentElement && michael@0: document.documentElement.namespaceURI === XUL_NS; michael@0: michael@0: /** michael@0: * Check if the given window is completely loaded. michael@0: * i.e. if its "load" event has already been fired and all possible DOM content michael@0: * is done loading (the whole DOM document, images content, ...) michael@0: * @params {nsIDOMWindow} window michael@0: */ michael@0: function isDocumentLoaded(window) { michael@0: return window.document.readyState == "complete"; michael@0: } michael@0: exports.isDocumentLoaded = isDocumentLoaded; michael@0: michael@0: function isBrowser(window) { michael@0: try { michael@0: return window.document.documentElement.getAttribute("windowtype") === BROWSER; michael@0: } michael@0: catch (e) {} michael@0: return false; michael@0: }; michael@0: exports.isBrowser = isBrowser; michael@0: michael@0: function getWindowTitle(window) { michael@0: return window && window.document ? window.document.title : null; michael@0: } michael@0: exports.getWindowTitle = getWindowTitle; michael@0: michael@0: function isXULBrowser(window) { michael@0: return !!(isBrowser(window) && window.XULBrowserWindow); michael@0: } michael@0: exports.isXULBrowser = isXULBrowser; michael@0: michael@0: /** michael@0: * Returns the most recent focused window michael@0: */ michael@0: function getFocusedWindow() { michael@0: let window = WM.getMostRecentWindow(BROWSER); michael@0: michael@0: return window ? window.document.commandDispatcher.focusedWindow : null; michael@0: } michael@0: exports.getFocusedWindow = getFocusedWindow; michael@0: michael@0: /** michael@0: * Returns the focused browser window if any, or the most recent one. michael@0: * Opening new window, updates most recent window, but focus window michael@0: * changes later; so most recent window and focused window are not always michael@0: * the same. michael@0: */ michael@0: function getFocusedBrowser() { michael@0: let window = FM.activeWindow; michael@0: return isBrowser(window) ? window : getMostRecentBrowserWindow() michael@0: } michael@0: exports.getFocusedBrowser = getFocusedBrowser; michael@0: michael@0: /** michael@0: * Returns the focused element in the most recent focused window michael@0: */ michael@0: function getFocusedElement() { michael@0: let window = WM.getMostRecentWindow(BROWSER); michael@0: michael@0: return window ? window.document.commandDispatcher.focusedElement : null; michael@0: } michael@0: exports.getFocusedElement = getFocusedElement; michael@0: michael@0: function getFrames(window) { michael@0: return Array.slice(window.frames).reduce(function(frames, frame) { michael@0: return frames.concat(frame, getFrames(frame)); michael@0: }, []); michael@0: } michael@0: exports.getFrames = getFrames; michael@0: michael@0: function getScreenPixelsPerCSSPixel(window) { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel; michael@0: } michael@0: exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel; michael@0: michael@0: function getOwnerBrowserWindow(node) { michael@0: /** michael@0: Takes DOM node and returns browser window that contains it. michael@0: **/ michael@0: let window = getToplevelWindow(node.ownerDocument.defaultView); michael@0: // If anchored window is browser then it's target browser window. michael@0: return isBrowser(window) ? window : null; michael@0: } michael@0: exports.getOwnerBrowserWindow = getOwnerBrowserWindow; michael@0: michael@0: function getParentWindow(window) { michael@0: try { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).parent michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: catch (e) {} michael@0: return null; michael@0: } michael@0: exports.getParentWindow = getParentWindow; michael@0: michael@0: michael@0: function getParentFrame(window) { michael@0: try { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).parent michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: catch (e) {} michael@0: return null; michael@0: } michael@0: exports.getParentWindow = getParentWindow; michael@0: michael@0: // The element in which the window is embedded, or `null` michael@0: // if the window is top-level. Similar to `window.frameElement` michael@0: // but can cross chrome-content boundries. michael@0: const getFrameElement = target => michael@0: (target instanceof Ci.nsIDOMDocument ? target.defaultView : target). michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils). michael@0: containerElement; michael@0: exports.getFrameElement = getFrameElement;