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: const { Cc, Ci, Cr } = require('chrome'), michael@0: { Trait } = require('../deprecated/traits'), michael@0: { List } = require('../deprecated/list'), michael@0: { EventEmitter } = require('../deprecated/events'), michael@0: { WindowTabs, WindowTabTracker } = require('./tabs-firefox'), michael@0: { WindowDom } = require('./dom'), michael@0: { WindowLoader } = require('./loader'), michael@0: { isBrowser, getWindowDocShell, windows: windowIterator } = require('../window/utils'), michael@0: { Options } = require('../tabs/common'), michael@0: apiUtils = require('../deprecated/api-utils'), michael@0: unload = require('../system/unload'), michael@0: windowUtils = require('../deprecated/window-utils'), michael@0: { WindowTrackerTrait } = windowUtils, michael@0: { ns } = require('../core/namespace'), michael@0: { observer: windowObserver } = require('./observer'), michael@0: { getOwnerWindow } = require('../private-browsing/window/utils'); michael@0: const { windowNS } = require('../window/namespace'); michael@0: const { isPrivateBrowsingSupported } = require('../self'); michael@0: const { ignoreWindow } = require('sdk/private-browsing/utils'); michael@0: const { viewFor } = require('../view/core'); michael@0: michael@0: /** michael@0: * Window trait composes safe wrappers for browser window that are E10S michael@0: * compatible. michael@0: */ michael@0: const BrowserWindowTrait = Trait.compose( michael@0: EventEmitter, michael@0: WindowDom.resolve({ close: '_close' }), michael@0: WindowTabs, michael@0: WindowTabTracker, michael@0: WindowLoader, michael@0: /* WindowSidebars, */ michael@0: Trait.compose({ michael@0: _emit: Trait.required, michael@0: _close: Trait.required, michael@0: _load: Trait.required, michael@0: /** michael@0: * Constructor returns wrapper of the specified chrome window. michael@0: * @param {nsIWindow} window michael@0: */ michael@0: constructor: function BrowserWindow(options) { michael@0: // Register this window ASAP, in order to avoid loop that would try michael@0: // to create this window instance over and over (see bug 648244) michael@0: windows.push(this); michael@0: michael@0: // make sure we don't have unhandled errors michael@0: this.on('error', console.exception.bind(console)); michael@0: michael@0: if ('onOpen' in options) michael@0: this.on('open', options.onOpen); michael@0: if ('onClose' in options) michael@0: this.on('close', options.onClose); michael@0: if ('onActivate' in options) michael@0: this.on('activate', options.onActivate); michael@0: if ('onDeactivate' in options) michael@0: this.on('deactivate', options.onDeactivate); michael@0: if ('window' in options) michael@0: this._window = options.window; michael@0: michael@0: if ('tabs' in options) { michael@0: this._tabOptions = Array.isArray(options.tabs) ? michael@0: options.tabs.map(Options) : michael@0: [ Options(options.tabs) ]; michael@0: } michael@0: else if ('url' in options) { michael@0: this._tabOptions = [ Options(options.url) ]; michael@0: } michael@0: michael@0: this._isPrivate = isPrivateBrowsingSupported && !!options.isPrivate; michael@0: michael@0: this._load(); michael@0: michael@0: windowNS(this._public).window = this._window; michael@0: getOwnerWindow.implement(this._public, getChromeWindow); michael@0: viewFor.implement(this._public, getChromeWindow); michael@0: michael@0: return this; michael@0: }, michael@0: destroy: function () this._onUnload(), michael@0: _tabOptions: [], michael@0: _onLoad: function() { michael@0: try { michael@0: this._initWindowTabTracker(); michael@0: this._loaded = true; michael@0: } michael@0: catch(e) { michael@0: this._emit('error', e); michael@0: } michael@0: michael@0: this._emitOnObject(browserWindows, 'open', this._public); michael@0: }, michael@0: _onUnload: function() { michael@0: if (!this._window) michael@0: return; michael@0: if (this._loaded) michael@0: this._destroyWindowTabTracker(); michael@0: michael@0: this._emitOnObject(browserWindows, 'close', this._public); michael@0: this._window = null; michael@0: windowNS(this._public).window = null; michael@0: // Removing reference from the windows array. michael@0: windows.splice(windows.indexOf(this), 1); michael@0: this._removeAllListeners(); michael@0: }, michael@0: close: function close(callback) { michael@0: // maybe we should deprecate this with message ? michael@0: if (callback) this.on('close', callback); michael@0: return this._close(); michael@0: } michael@0: }) michael@0: ); michael@0: michael@0: /** michael@0: * Gets a `BrowserWindowTrait` for the given `chromeWindow` if previously michael@0: * registered, `null` otherwise. michael@0: */ michael@0: function getRegisteredWindow(chromeWindow) { michael@0: for each (let window in windows) { michael@0: if (chromeWindow === window._window) michael@0: return window; michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: /** michael@0: * Wrapper for `BrowserWindowTrait`. Creates new instance if wrapper for michael@0: * window doesn't exists yet. If wrapper already exists then returns it michael@0: * instead. michael@0: * @params {Object} options michael@0: * Options that are passed to the the `BrowserWindowTrait` michael@0: * @returns {BrowserWindow} michael@0: * @see BrowserWindowTrait michael@0: */ michael@0: function BrowserWindow(options) { michael@0: let window = null; michael@0: michael@0: if ("window" in options) michael@0: window = getRegisteredWindow(options.window); michael@0: michael@0: return (window || BrowserWindowTrait(options))._public; michael@0: } michael@0: // to have proper `instanceof` behavior will go away when #596248 is fixed. michael@0: BrowserWindow.prototype = BrowserWindowTrait.prototype; michael@0: exports.BrowserWindow = BrowserWindow; michael@0: michael@0: const windows = []; michael@0: michael@0: const browser = ns(); michael@0: michael@0: function onWindowActivation (chromeWindow, event) { michael@0: if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window. michael@0: michael@0: let window = getRegisteredWindow(chromeWindow); michael@0: michael@0: if (window) michael@0: window._emit(event.type, window._public); michael@0: else michael@0: window = BrowserWindowTrait({ window: chromeWindow }); michael@0: michael@0: browser(browserWindows).internals._emit(event.type, window._public); michael@0: } michael@0: michael@0: windowObserver.on("activate", onWindowActivation); michael@0: windowObserver.on("deactivate", onWindowActivation); michael@0: michael@0: /** michael@0: * `BrowserWindows` trait is composed out of `List` trait and it represents michael@0: * "live" list of currently open browser windows. Instance mutates itself michael@0: * whenever new browser window gets opened / closed. michael@0: */ michael@0: // Very stupid to resolve all `toStrings` but this will be fixed by #596248 michael@0: const browserWindows = Trait.resolve({ toString: null }).compose( michael@0: List.resolve({ constructor: '_initList' }), michael@0: EventEmitter.resolve({ toString: null }), michael@0: WindowTrackerTrait.resolve({ constructor: '_initTracker', toString: null }), michael@0: Trait.compose({ michael@0: _emit: Trait.required, michael@0: _add: Trait.required, michael@0: _remove: Trait.required, michael@0: michael@0: // public API michael@0: michael@0: /** michael@0: * Constructor creates instance of `Windows` that represents live list of open michael@0: * windows. michael@0: */ michael@0: constructor: function BrowserWindows() { michael@0: browser(this._public).internals = this; michael@0: michael@0: this._trackedWindows = []; michael@0: this._initList(); michael@0: this._initTracker(); michael@0: unload.ensure(this, "_destructor"); michael@0: }, michael@0: _destructor: function _destructor() { michael@0: this._removeAllListeners('open'); michael@0: this._removeAllListeners('close'); michael@0: this._removeAllListeners('activate'); michael@0: this._removeAllListeners('deactivate'); michael@0: this._clear(); michael@0: michael@0: delete browser(this._public).internals; michael@0: }, michael@0: /** michael@0: * This property represents currently active window. michael@0: * Property is non-enumerable, in order to preserve array like enumeration. michael@0: * @type {Window|null} michael@0: */ michael@0: get activeWindow() { michael@0: let window = windowUtils.activeBrowserWindow; michael@0: // Bug 834961: ignore private windows when they are not supported michael@0: if (ignoreWindow(window)) michael@0: window = windowIterator()[0]; michael@0: return window ? BrowserWindow({window: window}) : null; michael@0: }, michael@0: open: function open(options) { michael@0: if (typeof options === "string") { michael@0: // `tabs` option is under review and may be removed. michael@0: options = { michael@0: tabs: [Options(options)], michael@0: isPrivate: isPrivateBrowsingSupported && options.isPrivate michael@0: }; michael@0: } michael@0: return BrowserWindow(options); michael@0: }, michael@0: michael@0: /** michael@0: * Internal listener which is called whenever new window gets open. michael@0: * Creates wrapper and adds to this list. michael@0: * @param {nsIWindow} chromeWindow michael@0: */ michael@0: _onTrack: function _onTrack(chromeWindow) { michael@0: if (!isBrowser(chromeWindow)) return; michael@0: let window = BrowserWindow({ window: chromeWindow }); michael@0: this._add(window); michael@0: this._emit('open', window); michael@0: }, michael@0: michael@0: /** michael@0: * Internal listener which is called whenever window gets closed. michael@0: * Cleans up references and removes wrapper from this list. michael@0: * @param {nsIWindow} window michael@0: */ michael@0: _onUntrack: function _onUntrack(chromeWindow) { michael@0: if (!isBrowser(chromeWindow)) return; michael@0: let window = BrowserWindow({ window: chromeWindow }); michael@0: this._remove(window); michael@0: this._emit('close', window); michael@0: michael@0: // Bug 724404: do not leak this module and linked windows: michael@0: // We have to do it on untrack and not only when `_onUnload` is called michael@0: // when windows are closed, otherwise, we will leak on addon disabling. michael@0: window.destroy(); michael@0: } michael@0: }).resolve({ toString: null }) michael@0: )(); michael@0: michael@0: function getChromeWindow(window) { michael@0: return windowNS(window).window; michael@0: } michael@0: michael@0: exports.browserWindows = browserWindows;