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': 'deprecated' michael@0: }; michael@0: michael@0: const { Cc, Ci } = require('chrome'); michael@0: const { EventEmitter } = require('../deprecated/events'); michael@0: const { Trait } = require('../deprecated/traits'); michael@0: const { when } = require('../system/unload'); michael@0: const events = require('../system/events'); michael@0: const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser, michael@0: getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils'); michael@0: const errors = require('../deprecated/errors'); michael@0: const { deprecateFunction } = require('../util/deprecate'); michael@0: const { ignoreWindow, isGlobalPBSupported } = require('sdk/private-browsing/utils'); michael@0: const { isPrivateBrowsingSupported } = require('../self'); 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: michael@0: // Bug 834961: ignore private windows when they are not supported michael@0: function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported }); michael@0: michael@0: /** michael@0: * An iterator for XUL windows currently in the application. michael@0: * michael@0: * @return A generator that yields XUL windows exposing the michael@0: * nsIDOMWindow interface. michael@0: */ michael@0: function windowIterator() { michael@0: // Bug 752631: We only pass already loaded window in order to avoid michael@0: // breaking XUL windows DOM. DOM is broken when some JS code try michael@0: // to access DOM during "uninitialized" state of the related document. michael@0: let list = getWindows().filter(isDocumentLoaded); michael@0: for (let i = 0, l = list.length; i < l; i++) { michael@0: yield list[i]; michael@0: } michael@0: }; michael@0: exports.windowIterator = windowIterator; michael@0: michael@0: /** michael@0: * An iterator for browser windows currently open in the application. michael@0: * @returns {Function} michael@0: * A generator that yields browser windows exposing the `nsIDOMWindow` michael@0: * interface. michael@0: */ michael@0: function browserWindowIterator() { michael@0: for each (let window in windowIterator()) { michael@0: if (isBrowser(window)) michael@0: yield window; michael@0: } michael@0: } michael@0: exports.browserWindowIterator = browserWindowIterator; michael@0: michael@0: function WindowTracker(delegate) { michael@0: if (!(this instanceof WindowTracker)) { michael@0: return new WindowTracker(delegate); michael@0: } michael@0: michael@0: this._delegate = delegate; michael@0: this._loadingWindows = []; michael@0: michael@0: for each (let window in getWindows()) michael@0: this._regWindow(window); michael@0: windowWatcher.registerNotification(this); michael@0: this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this); michael@0: events.on('toplevel-window-ready', this._onToplevelWindowReady); michael@0: michael@0: require('../system/unload').ensure(this); michael@0: michael@0: return this; michael@0: }; michael@0: michael@0: WindowTracker.prototype = { michael@0: _regLoadingWindow: function _regLoadingWindow(window) { michael@0: // Bug 834961: ignore private windows when they are not supported michael@0: if (ignoreWindow(window)) michael@0: return; michael@0: michael@0: this._loadingWindows.push(window); michael@0: window.addEventListener('load', this, true); michael@0: }, michael@0: michael@0: _unregLoadingWindow: function _unregLoadingWindow(window) { michael@0: var index = this._loadingWindows.indexOf(window); michael@0: michael@0: if (index != -1) { michael@0: this._loadingWindows.splice(index, 1); michael@0: window.removeEventListener('load', this, true); michael@0: } michael@0: }, michael@0: michael@0: _regWindow: function _regWindow(window) { michael@0: // Bug 834961: ignore private windows when they are not supported michael@0: if (ignoreWindow(window)) michael@0: return; michael@0: michael@0: if (window.document.readyState == 'complete') { michael@0: this._unregLoadingWindow(window); michael@0: this._delegate.onTrack(window); michael@0: } else michael@0: this._regLoadingWindow(window); michael@0: }, michael@0: michael@0: _unregWindow: function _unregWindow(window) { michael@0: if (window.document.readyState == 'complete') { michael@0: if (this._delegate.onUntrack) michael@0: this._delegate.onUntrack(window); michael@0: } else { michael@0: this._unregLoadingWindow(window); michael@0: } michael@0: }, michael@0: michael@0: unload: function unload() { michael@0: windowWatcher.unregisterNotification(this); michael@0: events.off('toplevel-window-ready', this._onToplevelWindowReady); michael@0: for each (let window in getWindows()) michael@0: this._unregWindow(window); michael@0: }, michael@0: michael@0: handleEvent: errors.catchAndLog(function handleEvent(event) { michael@0: if (event.type == 'load' && event.target) { michael@0: var window = event.target.defaultView; michael@0: if (window) michael@0: this._regWindow(window); michael@0: } michael@0: }), michael@0: michael@0: _onToplevelWindowReady: function _onToplevelWindowReady({subject}) { michael@0: let window = subject; michael@0: // ignore private windows if they are not supported michael@0: if (ignoreWindow(window)) michael@0: return; michael@0: this._regWindow(window); michael@0: }, michael@0: michael@0: observe: errors.catchAndLog(function observe(subject, topic, data) { michael@0: var window = subject.QueryInterface(Ci.nsIDOMWindow); michael@0: // ignore private windows if they are not supported michael@0: if (ignoreWindow(window)) michael@0: return; michael@0: if (topic == 'domwindowclosed') michael@0: this._unregWindow(window); michael@0: }) michael@0: }; michael@0: exports.WindowTracker = WindowTracker; michael@0: michael@0: const WindowTrackerTrait = Trait.compose({ michael@0: _onTrack: Trait.required, michael@0: _onUntrack: Trait.required, michael@0: constructor: function WindowTrackerTrait() { michael@0: WindowTracker({ michael@0: onTrack: this._onTrack.bind(this), michael@0: onUntrack: this._onUntrack.bind(this) michael@0: }); michael@0: } michael@0: }); michael@0: exports.WindowTrackerTrait = WindowTrackerTrait; michael@0: michael@0: var gDocsToClose = []; michael@0: michael@0: function onDocUnload(event) { michael@0: var index = gDocsToClose.indexOf(event.target); michael@0: if (index == -1) michael@0: throw new Error('internal error: unloading document not found'); michael@0: var document = gDocsToClose.splice(index, 1)[0]; michael@0: // Just in case, let's remove the event listener too. michael@0: document.defaultView.removeEventListener('unload', onDocUnload, false); michael@0: } michael@0: michael@0: onDocUnload = require('./errors').catchAndLog(onDocUnload); michael@0: michael@0: exports.closeOnUnload = function closeOnUnload(window) { michael@0: window.addEventListener('unload', onDocUnload, false); michael@0: gDocsToClose.push(window.document); michael@0: }; michael@0: michael@0: Object.defineProperties(exports, { michael@0: activeWindow: { michael@0: enumerable: true, michael@0: get: function() { michael@0: return getMostRecentWindow(null); michael@0: }, michael@0: set: function(window) { michael@0: try { michael@0: window.focus(); michael@0: } catch (e) {} michael@0: } michael@0: }, michael@0: activeBrowserWindow: { michael@0: enumerable: true, michael@0: get: getMostRecentBrowserWindow michael@0: } michael@0: }); michael@0: michael@0: michael@0: /** michael@0: * Returns the ID of the window's current inner window. michael@0: */ michael@0: exports.getInnerId = deprecateFunction(getInnerId, michael@0: 'require("window-utils").getInnerId is deprecated, ' + michael@0: 'please use require("sdk/window/utils").getInnerId instead' michael@0: ); michael@0: michael@0: exports.getOuterId = deprecateFunction(getOuterId, michael@0: 'require("window-utils").getOuterId is deprecated, ' + michael@0: 'please use require("sdk/window/utils").getOuterId instead' michael@0: ); michael@0: michael@0: exports.isBrowser = deprecateFunction(isBrowser, michael@0: 'require("window-utils").isBrowser is deprecated, ' + michael@0: 'please use require("sdk/window/utils").isBrowser instead' michael@0: ); michael@0: michael@0: exports.hiddenWindow = appShellService.hiddenDOMWindow; michael@0: michael@0: when( michael@0: function() { michael@0: gDocsToClose.slice().forEach( michael@0: function(doc) { doc.defaultView.close(); }); michael@0: });