Files
tubestation/addon-sdk/source/lib/sdk/windows/tabs-firefox.js
2014-03-03 11:22:07 -08:00

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;