summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/window
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/window')
-rw-r--r--toolkit/jetpack/sdk/window/browser.js54
-rw-r--r--toolkit/jetpack/sdk/window/events.js68
-rw-r--r--toolkit/jetpack/sdk/window/helpers.js81
-rw-r--r--toolkit/jetpack/sdk/window/namespace.js6
-rw-r--r--toolkit/jetpack/sdk/window/utils.js460
5 files changed, 669 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/window/browser.js b/toolkit/jetpack/sdk/window/browser.js
new file mode 100644
index 000000000..380b5a486
--- /dev/null
+++ b/toolkit/jetpack/sdk/window/browser.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Class } = require('../core/heritage');
+const { windowNS } = require('./namespace');
+const { on, off, once } = require('../event/core');
+const { method } = require('../lang/functional');
+const { getWindowTitle } = require('./utils');
+const unload = require('../system/unload');
+const { EventTarget } = require('../event/target');
+const { isPrivate } = require('../private-browsing/utils');
+const { isWindowPrivate, isFocused } = require('../window/utils');
+const { viewFor } = require('../view/core');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';
+
+const BrowserWindow = Class({
+ initialize: function initialize(options) {
+ EventTarget.prototype.initialize.call(this, options);
+ windowNS(this).window = options.window;
+ },
+ activate: function activate() {
+ // TODO
+ return null;
+ },
+ close: function() {
+ throw new Error(ERR_FENNEC_MSG);
+ return null;
+ },
+ get title() {
+ return getWindowTitle(windowNS(this).window);
+ },
+ // NOTE: Fennec only has one window, which is assumed below
+ // TODO: remove assumption below
+ // NOTE: tabs requires windows
+ get tabs() {
+ return require('../tabs');
+ },
+ get activeTab() {
+ return require('../tabs').activeTab;
+ },
+ on: method(on),
+ removeListener: method(off),
+ once: method(once)
+});
+exports.BrowserWindow = BrowserWindow;
+
+const getWindowView = window => windowNS(window).window;
+
+viewFor.define(BrowserWindow, getWindowView);
+isPrivate.define(BrowserWindow, (window) => isWindowPrivate(viewFor(window).window));
+isFocused.define(BrowserWindow, (window) => isFocused(viewFor(window).window));
diff --git a/toolkit/jetpack/sdk/window/events.js b/toolkit/jetpack/sdk/window/events.js
new file mode 100644
index 000000000..b1d3a1f3e
--- /dev/null
+++ b/toolkit/jetpack/sdk/window/events.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Ci, Cu } = require("chrome");
+const { observe } = require("../event/chrome");
+const { open } = require("../event/dom");
+const { windows } = require("../window/utils");
+const { filter, merge, map, expand } = require("../event/utils");
+
+function documentMatches(weakWindow, event) {
+ let window = weakWindow.get();
+ return window && event.target === window.document;
+}
+
+function makeStrictDocumentFilter(window) {
+ // Note: Do not define a closure within this function. Otherwise
+ // you may leak the window argument.
+ let weak = Cu.getWeakReference(window);
+ return documentMatches.bind(null, weak);
+}
+
+function toEventWithDefaultViewTarget({type, target}) {
+ return { type: type, target: target.defaultView }
+}
+
+// Function registers single shot event listeners for relevant window events
+// that forward events to exported event stream.
+function eventsFor(window) {
+ // NOTE: Do no use pass a closure from this function into a stream
+ // transform function. You will capture the window in the
+ // closure and leak the window until the event stream is
+ // completely closed.
+ let interactive = open(window, "DOMContentLoaded", { capture: true });
+ let complete = open(window, "load", { capture: true });
+ let states = merge([interactive, complete]);
+ let changes = filter(states, makeStrictDocumentFilter(window));
+ return map(changes, toEventWithDefaultViewTarget);
+}
+
+// Create our event channels. We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+ // In addition to observing windows that are open we also observe windows
+ // that are already already opened in case they're in process of loading.
+ var opened = windows(null, { includePrivate: true });
+ var currentEvents = merge(opened.map(eventsFor));
+
+ // Register system event listeners for top level window open / close.
+ function rename({type, target, data}) {
+ return { type: rename[type], target: target, data: data }
+ }
+ rename.domwindowopened = "open";
+ rename.domwindowclosed = "close";
+
+ var openEvents = map(observe("domwindowopened"), rename);
+ var closeEvents = map(observe("domwindowclosed"), rename);
+ var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
+
+ return merge([currentEvents, futureEvents, openEvents, closeEvents]);
+}
+
+exports.events = makeEvents();
diff --git a/toolkit/jetpack/sdk/window/helpers.js b/toolkit/jetpack/sdk/window/helpers.js
new file mode 100644
index 000000000..56cfcaba7
--- /dev/null
+++ b/toolkit/jetpack/sdk/window/helpers.js
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { defer, all } = require('../core/promise');
+const events = require('../system/events');
+const { open: openWindow, onFocus, getToplevelWindow,
+ isInteractive, isStartupFinished, getOuterId } = require('./utils');
+const { Ci } = require("chrome");
+
+function open(uri, options) {
+ return promise(openWindow.apply(null, arguments), 'load').then(focus);
+}
+exports.open = open;
+
+function close(window) {
+ let deferred = defer();
+ let toplevelWindow = getToplevelWindow(window);
+ let outerId = getOuterId(toplevelWindow);
+ events.on("outer-window-destroyed", function onclose({subject}) {
+ let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (id == outerId) {
+ events.off("outer-window-destroyed", onclose);
+ deferred.resolve();
+ }
+ }, true);
+ window.close();
+ return deferred.promise;
+}
+exports.close = close;
+
+function focus(window) {
+ let p = onFocus(window);
+ window.focus();
+ return p;
+}
+exports.focus = focus;
+
+function ready(window) {
+ let { promise: result, resolve } = defer();
+
+ if (isInteractive(window))
+ resolve(window);
+ else
+ resolve(promise(window, 'DOMContentLoaded'));
+
+ return result;
+}
+exports.ready = ready;
+
+function startup(window) {
+ let { promise: result, resolve } = defer();
+
+ if (isStartupFinished(window)) {
+ resolve(window);
+ } else {
+ events.on("browser-delayed-startup-finished", function listener({subject}) {
+ if (subject === window) {
+ events.off("browser-delayed-startup-finished", listener);
+ resolve(window);
+ }
+ });
+ }
+
+ return result;
+}
+exports.startup = startup;
+
+function promise(target, evt, capture) {
+ let deferred = defer();
+ capture = !!capture;
+
+ target.addEventListener(evt, function eventHandler() {
+ target.removeEventListener(evt, eventHandler, capture);
+ deferred.resolve(target);
+ }, capture);
+
+ return deferred.promise;
+}
+exports.promise = promise;
diff --git a/toolkit/jetpack/sdk/window/namespace.js b/toolkit/jetpack/sdk/window/namespace.js
new file mode 100644
index 000000000..b486f888d
--- /dev/null
+++ b/toolkit/jetpack/sdk/window/namespace.js
@@ -0,0 +1,6 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+exports.windowNS = require('../core/namespace').ns();
diff --git a/toolkit/jetpack/sdk/window/utils.js b/toolkit/jetpack/sdk/window/utils.js
new file mode 100644
index 000000000..db91a0fed
--- /dev/null
+++ b/toolkit/jetpack/sdk/window/utils.js
@@ -0,0 +1,460 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci } = require('chrome');
+const array = require('../util/array');
+const { defer } = require('sdk/core/promise');
+const { dispatcher } = require("../util/dispatcher");
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+const io = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+const FM = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+const prefs = require("../preferences/service");
+const BROWSER = 'navigator:browser',
+ URI_BROWSER = prefs.get('browser.chromeURL', null),
+ NAME = '_blank',
+ FEATURES = 'chrome,all,dialog=no,non-private';
+
+function isWindowPrivate(win) {
+ if (!win)
+ return false;
+
+ // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
+ // and the app is Firefox, then assume per-window private browsing is
+ // enabled.
+ try {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing;
+ }
+ catch(e) {}
+
+ // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw.
+ try {
+ return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
+ }
+ catch (e) {}
+
+ return false;
+}
+exports.isWindowPrivate = isWindowPrivate;
+
+function getMostRecentBrowserWindow() {
+ return getMostRecentWindow(BROWSER);
+}
+exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
+
+function getHiddenWindow() {
+ return appShellService.hiddenDOMWindow;
+}
+exports.getHiddenWindow = getHiddenWindow;
+
+function getMostRecentWindow(type) {
+ return WM.getMostRecentWindow(type);
+}
+exports.getMostRecentWindow = getMostRecentWindow;
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+function getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+exports.getInnerId = getInnerId;
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+exports.getOuterId = getOuterId;
+
+/**
+ * Returns window by the outer window id.
+ */
+const getByOuterId = WM.getOuterWindowWithId;
+exports.getByOuterId = getByOuterId;
+
+const getByInnerId = WM.getCurrentInnerWindowWithId;
+exports.getByInnerId = getByInnerId;
+
+/**
+ * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
+ */
+function getXULWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIXULWindow);
+};
+exports.getXULWindow = getXULWindow;
+
+function getDOMWindow(xulWindow) {
+ return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindow);
+}
+exports.getDOMWindow = getDOMWindow;
+
+/**
+ * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
+ */
+function getBaseWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.
+ QueryInterface(Ci.nsIBaseWindow);
+}
+exports.getBaseWindow = getBaseWindow;
+
+/**
+ * Returns the `nsIDOMWindow` toplevel window for any child/inner window
+ */
+function getToplevelWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+}
+exports.getToplevelWindow = getToplevelWindow;
+
+function getWindowDocShell(window) {
+ return window.gBrowser.docShell;
+}
+exports.getWindowDocShell = getWindowDocShell;
+
+function getWindowLoadingContext(window) {
+ return getWindowDocShell(window).
+ QueryInterface(Ci.nsILoadContext);
+}
+exports.getWindowLoadingContext = getWindowLoadingContext;
+
+const isTopLevel = window => window && getToplevelWindow(window) === window;
+exports.isTopLevel = isTopLevel;
+
+/**
+ * Takes hash of options and serializes it to a features string that
+ * can be used passed to `window.open`. For more details on features string see:
+ * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
+ */
+function serializeFeatures(options) {
+ return Object.keys(options).reduce(function(result, name) {
+ let value = options[name];
+
+ // the chrome and private features are special
+ if ((name == 'private' || name == 'chrome' || name == 'all'))
+ return result + ((value === true) ? ',' + name : '');
+
+ return result + ',' + name + '=' +
+ (value === true ? 'yes' : value === false ? 'no' : value);
+ }, '').substr(1);
+}
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * @params {String} uri
+ * URI of the document to be loaded into window.
+ * @params {nsIDOMWindow} options.parent
+ * Used as parent for the created window.
+ * @params {String} options.name
+ * Optional name that is assigned to the window.
+ * @params {Object} options.features
+ * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
+ */
+function open(uri, options) {
+ uri = uri || URI_BROWSER;
+ options = options || {};
+
+ if (!uri)
+ throw new Error('browser.chromeURL is undefined, please provide an explicit uri');
+
+ if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
+ throw new Error('only chrome, resource and data uris are allowed');
+
+ let newWindow = windowWatcher.
+ openWindow(options.parent || null,
+ uri,
+ options.name || null,
+ options.features ? serializeFeatures(options.features) : null,
+ options.args || null);
+
+ return newWindow;
+}
+exports.open = open;
+
+function onFocus(window) {
+ let { resolve, promise } = defer();
+
+ if (isFocused(window)) {
+ resolve(window);
+ }
+ else {
+ window.addEventListener("focus", function focusListener() {
+ window.removeEventListener("focus", focusListener, true);
+ resolve(window);
+ }, true);
+ }
+
+ return promise;
+}
+exports.onFocus = onFocus;
+
+var isFocused = dispatcher("window-isFocused");
+isFocused.when(x => x instanceof Ci.nsIDOMWindow, (window) => {
+ const FM = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+ let childTargetWindow = {};
+ FM.getFocusedElementForWindow(window, true, childTargetWindow);
+ childTargetWindow = childTargetWindow.value;
+
+ let focusedChildWindow = {};
+ if (FM.activeWindow) {
+ FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow);
+ focusedChildWindow = focusedChildWindow.value;
+ }
+
+ return (focusedChildWindow === childTargetWindow);
+});
+exports.isFocused = isFocused;
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * Same as `open` but with more features
+ * @param {Object} options
+ *
+ */
+function openDialog(options) {
+ options = options || {};
+
+ let features = options.features || FEATURES;
+ let featureAry = features.toLowerCase().split(',');
+
+ if (!!options.private) {
+ // add private flag if private window is desired
+ if (!array.has(featureAry, 'private')) {
+ featureAry.push('private');
+ }
+
+ // remove the non-private flag ig a private window is desired
+ let nonPrivateIndex = featureAry.indexOf('non-private');
+ if (nonPrivateIndex >= 0) {
+ featureAry.splice(nonPrivateIndex, 1);
+ }
+
+ features = featureAry.join(',');
+ }
+
+ let browser = getMostRecentBrowserWindow();
+
+ // if there is no browser then do nothing
+ if (!browser)
+ return undefined;
+
+ let newWindow = browser.openDialog.apply(
+ browser,
+ array.flatten([
+ options.url || URI_BROWSER,
+ options.name || NAME,
+ features,
+ options.args || null
+ ])
+ );
+
+ return newWindow;
+}
+exports.openDialog = openDialog;
+
+/**
+ * Returns an array of all currently opened windows.
+ * Note that these windows may still be loading.
+ */
+function windows(type, options) {
+ options = options || {};
+ let list = [];
+ let winEnum = WM.getEnumerator(type);
+ while (winEnum.hasMoreElements()) {
+ let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+ // Only add non-private windows when pb permission isn't set,
+ // unless an option forces the addition of them.
+ if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) {
+ list.push(window);
+ }
+ }
+ return list;
+}
+exports.windows = windows;
+
+/**
+ * Check if the given window is interactive.
+ * i.e. if its "DOMContentLoaded" event has already been fired.
+ * @params {nsIDOMWindow} window
+ */
+const isInteractive = window =>
+ window.document.readyState === "interactive" ||
+ isDocumentLoaded(window) ||
+ // XUL documents stays '"uninitialized"' until it's `readyState` becomes
+ // `"complete"`.
+ isXULDocumentWindow(window) && window.document.readyState === "interactive";
+exports.isInteractive = isInteractive;
+
+/**
+ * Check if the given browser window has finished the startup.
+ * @params {nsIDOMWindow} window
+ */
+const isStartupFinished = (window) =>
+ isBrowser(window) &&
+ window.gBrowserInit &&
+ window.gBrowserInit.delayedStartupFinished;
+
+exports.isStartupFinished = isStartupFinished;
+
+const isXULDocumentWindow = ({document}) =>
+ document.documentElement &&
+ document.documentElement.namespaceURI === XUL_NS;
+
+/**
+ * Check if the given window is completely loaded.
+ * i.e. if its "load" event has already been fired and all possible DOM content
+ * is done loading (the whole DOM document, images content, ...)
+ * @params {nsIDOMWindow} window
+ */
+function isDocumentLoaded(window) {
+ return window.document.readyState == "complete";
+}
+exports.isDocumentLoaded = isDocumentLoaded;
+
+function isBrowser(window) {
+ try {
+ return window.document.documentElement.getAttribute("windowtype") === BROWSER;
+ }
+ catch (e) {}
+ return false;
+};
+exports.isBrowser = isBrowser;
+
+function getWindowTitle(window) {
+ return window && window.document ? window.document.title : null;
+}
+exports.getWindowTitle = getWindowTitle;
+
+function isXULBrowser(window) {
+ return !!(isBrowser(window) && window.XULBrowserWindow);
+}
+exports.isXULBrowser = isXULBrowser;
+
+/**
+ * Returns the most recent focused window
+ */
+function getFocusedWindow() {
+ let window = WM.getMostRecentWindow(BROWSER);
+
+ return window ? window.document.commandDispatcher.focusedWindow : null;
+}
+exports.getFocusedWindow = getFocusedWindow;
+
+/**
+ * Returns the focused browser window if any, or the most recent one.
+ * Opening new window, updates most recent window, but focus window
+ * changes later; so most recent window and focused window are not always
+ * the same.
+ */
+function getFocusedBrowser() {
+ let window = FM.activeWindow;
+ return isBrowser(window) ? window : getMostRecentBrowserWindow()
+}
+exports.getFocusedBrowser = getFocusedBrowser;
+
+/**
+ * Returns the focused element in the most recent focused window
+ */
+function getFocusedElement() {
+ let window = WM.getMostRecentWindow(BROWSER);
+
+ return window ? window.document.commandDispatcher.focusedElement : null;
+}
+exports.getFocusedElement = getFocusedElement;
+
+function getFrames(window) {
+ return Array.slice(window.frames).reduce(function(frames, frame) {
+ return frames.concat(frame, getFrames(frame));
+ }, []);
+}
+exports.getFrames = getFrames;
+
+function getScreenPixelsPerCSSPixel(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel;
+}
+exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel;
+
+function getOwnerBrowserWindow(node) {
+ /**
+ Takes DOM node and returns browser window that contains it.
+ **/
+ let window = getToplevelWindow(node.ownerDocument.defaultView);
+ // If anchored window is browser then it's target browser window.
+ return isBrowser(window) ? window : null;
+}
+exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
+
+function getParentWindow(window) {
+ try {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).parent
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ }
+ catch (e) {}
+ return null;
+}
+exports.getParentWindow = getParentWindow;
+
+
+function getParentFrame(window) {
+ try {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).parent
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ }
+ catch (e) {}
+ return null;
+}
+exports.getParentWindow = getParentWindow;
+
+// The element in which the window is embedded, or `null`
+// if the window is top-level. Similar to `window.frameElement`
+// but can cross chrome-content boundries.
+const getFrameElement = target =>
+ (target instanceof Ci.nsIDOMDocument ? target.defaultView : target).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ containerElement;
+exports.getFrameElement = getFrameElement;