217 lines
7.6 KiB
JavaScript
217 lines
7.6 KiB
JavaScript
/* 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 { Trait } = require("../deprecated/traits");
|
|
const { List } = require("../deprecated/list");
|
|
const { Tab } = require("../tabs/tab");
|
|
const { EventEmitter } = require("../deprecated/events");
|
|
const { EVENTS } = require("../tabs/events");
|
|
const { getOwnerWindow, getActiveTab, getTabs,
|
|
openTab } = require("../tabs/utils");
|
|
const { Options } = require("../tabs/common");
|
|
const { observer: tabsObserver } = require("../tabs/observer");
|
|
const { ignoreWindow } = require("../private-browsing/utils");
|
|
const { when: unload } = require('../system/unload');
|
|
|
|
const TAB_BROWSER = "tabbrowser";
|
|
|
|
let unloaded = false;
|
|
unload(_ => unloaded = true);
|
|
|
|
/**
|
|
* This is a trait that is used in composition of window wrapper. Trait tracks
|
|
* tab related events of the wrapped window in order to keep track of open
|
|
* tabs and maintain their wrappers. Every new tab gets wrapped and jetpack
|
|
* type event is emitted.
|
|
*/
|
|
const WindowTabTracker = Trait.compose({
|
|
/**
|
|
* Chrome window whose tabs are tracked.
|
|
*/
|
|
_window: Trait.required,
|
|
/**
|
|
* Function used to emit events.
|
|
*/
|
|
_emit: EventEmitter.required,
|
|
_tabOptions: Trait.required,
|
|
/**
|
|
* Function to add event listeners.
|
|
*/
|
|
on: EventEmitter.required,
|
|
removeListener: EventEmitter.required,
|
|
/**
|
|
* Initializes tab tracker for a browser window.
|
|
*/
|
|
_initWindowTabTracker: function _initWindowTabTracker() {
|
|
// Ugly hack that we have to remove at some point (see Bug 658059). At this
|
|
// point it is necessary to invoke lazy `tabs` getter on the windows object
|
|
// which creates a `TabList` instance.
|
|
this.tabs;
|
|
|
|
// Binding all methods used as event listeners to the instance.
|
|
this._onTabReady = this._emitEvent.bind(this, "ready");
|
|
this._onTabLoad = this._emitEvent.bind(this, "load");
|
|
this._onTabPageShow = this._emitEvent.bind(this, "pageshow");
|
|
this._onTabOpen = this._onTabEvent.bind(this, "open");
|
|
this._onTabClose = this._onTabEvent.bind(this, "close");
|
|
this._onTabActivate = this._onTabEvent.bind(this, "activate");
|
|
this._onTabDeactivate = this._onTabEvent.bind(this, "deactivate");
|
|
this._onTabPinned = this._onTabEvent.bind(this, "pinned");
|
|
this._onTabUnpinned = this._onTabEvent.bind(this, "unpinned");
|
|
|
|
for each (let tab in getTabs(this._window)) {
|
|
// We emulate "open" events for all open tabs since gecko does not emits
|
|
// them on the tabs that new windows are open with. Also this is
|
|
// necessary to synchronize tabs lists with an actual state.
|
|
this._onTabOpen(tab);
|
|
}
|
|
|
|
// We also emulate "activate" event so that it's picked up by a tab list.
|
|
this._onTabActivate(getActiveTab(this._window));
|
|
|
|
// Setting up event listeners
|
|
tabsObserver.on("open", this._onTabOpen);
|
|
tabsObserver.on("close", this._onTabClose);
|
|
tabsObserver.on("activate", this._onTabActivate);
|
|
tabsObserver.on("deactivate", this._onTabDeactivate);
|
|
tabsObserver.on("pinned", this._onTabPinned);
|
|
tabsObserver.on("unpinned", this._onTabUnpinned);
|
|
},
|
|
_destroyWindowTabTracker: function _destroyWindowTabTracker() {
|
|
// We emulate close events on all tabs, since gecko does not emits such
|
|
// events by itself.
|
|
for each (let tab in this.tabs)
|
|
this._emitEvent("close", tab);
|
|
|
|
this._tabs._clear();
|
|
|
|
tabsObserver.removeListener("open", this._onTabOpen);
|
|
tabsObserver.removeListener("close", this._onTabClose);
|
|
tabsObserver.removeListener("activate", this._onTabActivate);
|
|
tabsObserver.removeListener("deactivate", this._onTabDeactivate);
|
|
},
|
|
_onTabEvent: function _onTabEvent(type, tab) {
|
|
// Accept only tabs for the watched window, and ignore private tabs
|
|
// if addon doesn't have private permission
|
|
if (this._window === getOwnerWindow(tab) && !ignoreWindow(this._window)) {
|
|
let options = this._tabOptions.shift() || {};
|
|
options.tab = tab;
|
|
options.window = this._public;
|
|
|
|
// Ignore zombie tabs on open that have already been removed
|
|
if (type == "open" && !tab.linkedBrowser)
|
|
return;
|
|
|
|
// Create a tab wrapper on open event, otherwise, just fetch existing
|
|
// tab object
|
|
let wrappedTab = Tab(options, type !== "open");
|
|
if (!wrappedTab)
|
|
return;
|
|
|
|
// Setting up an event listener for ready events.
|
|
if (type === "open") {
|
|
wrappedTab.on("ready", this._onTabReady);
|
|
wrappedTab.on("load", this._onTabLoad);
|
|
wrappedTab.on("pageshow", this._onTabPageShow);
|
|
}
|
|
|
|
this._emitEvent(type, wrappedTab);
|
|
}
|
|
},
|
|
_emitEvent: function _emitEvent(type, tag) {
|
|
if (unloaded)
|
|
return;
|
|
|
|
// Slices additional arguments and passes them into exposed
|
|
// listener like other events (for pageshow)
|
|
let args = Array.slice(arguments);
|
|
// Notifies combined tab list that tab was added / removed.
|
|
tabs._emit.apply(tabs, args);
|
|
// Notifies contained tab list that window was added / removed.
|
|
this._tabs._emit.apply(this._tabs, args);
|
|
}
|
|
});
|
|
exports.WindowTabTracker = WindowTabTracker;
|
|
|
|
/**
|
|
* This trait is used to create live representation of open tab lists. Each
|
|
* window wrapper's tab list is represented by an object created from this
|
|
* trait. It is also used to represent list of all the open windows. Trait is
|
|
* composed out of `EventEmitter` in order to emit 'TabOpen', 'TabClose' events.
|
|
* **Please note** that objects created by this trait can't be exposed outside
|
|
* instead you should expose it's `_public` property, see comments in
|
|
* constructor for details.
|
|
*/
|
|
const TabList = List.resolve({ constructor: "_init" }).compose(
|
|
// This is ugly, but necessary. Will be removed by #596248
|
|
EventEmitter.resolve({ toString: null }),
|
|
Trait.compose({
|
|
on: Trait.required,
|
|
_emit: Trait.required,
|
|
constructor: function TabList(options) {
|
|
this._window = options.window;
|
|
// Add new items to the list
|
|
this.on(EVENTS.open.name, this._add.bind(this));
|
|
|
|
// Remove closed items from the list
|
|
this.on(EVENTS.close.name, this._remove.bind(this));
|
|
|
|
// Set value whenever new tab becomes active.
|
|
this.on("activate", function onTabActivate(tab) {
|
|
this._activeTab = tab;
|
|
}.bind(this));
|
|
|
|
// Initialize list.
|
|
this._init();
|
|
// This list is not going to emit any events, object holding this list
|
|
// will do it instead, to make that possible we return a private API.
|
|
return this;
|
|
},
|
|
get activeTab() this._activeTab,
|
|
_activeTab: null,
|
|
|
|
open: function open(options) {
|
|
let window = this._window;
|
|
let chromeWindow = window._window;
|
|
options = Options(options);
|
|
|
|
// save the tab options
|
|
window._tabOptions.push(options);
|
|
// open the tab
|
|
let tab = openTab(chromeWindow, options.url, options);
|
|
}
|
|
// This is ugly, but necessary. Will be removed by #596248
|
|
}).resolve({ toString: null })
|
|
);
|
|
|
|
/**
|
|
* Combined list of all open tabs on all the windows.
|
|
* type {TabList}
|
|
*/
|
|
var tabs = TabList({ window: null });
|
|
exports.tabs = tabs._public;
|
|
|
|
/**
|
|
* Trait is a part of composition that represents window wrapper. This trait is
|
|
* composed out of `WindowTabTracker` that allows it to keep track of open tabs
|
|
* on the window being wrapped.
|
|
*/
|
|
const WindowTabs = Trait.compose(
|
|
WindowTabTracker,
|
|
Trait.compose({
|
|
_window: Trait.required,
|
|
/**
|
|
* List of tabs
|
|
*/
|
|
get tabs() (this._tabs || (this._tabs = TabList({ window: this })))._public,
|
|
_tabs: null,
|
|
})
|
|
);
|
|
exports.WindowTabs = WindowTabs;
|