diff options
Diffstat (limited to 'tools/addon-sdk-1.7/packages/api-utils/lib/tabs')
4 files changed, 442 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js new file mode 100644 index 0000000..efb8b45 --- /dev/null +++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/events.js @@ -0,0 +1,26 @@ +/* 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 ON_PREFIX = "on"; +const TAB_PREFIX = "Tab"; + +const EVENTS = { + ready: "DOMContentLoaded", + open: "TabOpen", + close: "TabClose", + activate: "TabSelect", + deactivate: null, + pinned: "TabPinned", + unpinned: "TabUnpinned" +} +exports.EVENTS = EVENTS; + +Object.keys(EVENTS).forEach(function(name) { + EVENTS[name] = { + name: name, + listener: ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1), + dom: EVENTS[name] + } +}); diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js new file mode 100644 index 0000000..e3854cb --- /dev/null +++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/observer.js @@ -0,0 +1,93 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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 { EventEmitterTrait: EventEmitter } = require("../events"); +const { DOMEventAssembler } = require("../events/assembler"); +const { Trait } = require("../light-traits"); +const { getActiveTab, getTabs, getTabContainers } = require("./utils"); +const { browserWindowIterator, isBrowser } = require("../window-utils"); +const { observer: windowObserver } = require("../windows/observer"); + +const EVENTS = { + "TabOpen": "open", + "TabClose": "close", + "TabSelect": "select", + "TabMove": "move", + "TabPinned": "pinned", + "TabUnpinned": "unpinned" +}; + + +// Event emitter objects used to register listeners and emit events on them +// when they occur. +const observer = Trait.compose(DOMEventAssembler, EventEmitter).create({ + /** + * Method is implemented by `EventEmitter` and is used just for emitting + * events on registered listeners. + */ + _emit: Trait.required, + /** + * Events that are supported and emitted by the module. + */ + supportedEventsTypes: Object.keys(EVENTS), + /** + * Function handles all the supported events on all the windows that are + * observed. Method is used to proxy events to the listeners registered on + * this event emitter. + * @param {Event} event + * Keyboard event being emitted. + */ + handleEvent: function handleEvent(event) { + this._emit(EVENTS[event.type], event.target, event); + } +}); + +// Currently gecko does not dispatches any event on the previously selected +// tab before / after "TabSelect" is dispatched. In order to work around this +// limitation we keep track of selected tab and emit "deactivate" event with +// that before emitting "activate" on selected tab. +var selectedTab = null; +function onTabSelect(tab) { + if (selectedTab !== tab) { + if (selectedTab) observer._emit("deactivate", selectedTab); + if (tab) observer._emit("activate", selectedTab = tab); + } +}; +observer.on("select", onTabSelect); + +// We also observe opening / closing windows in order to add / remove it's +// containers to the observed list. +function onWindowOpen(chromeWindow) { + if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window. + getTabContainers(chromeWindow).forEach(function (container) { + observer.observe(container); + }); +} +windowObserver.on("open", onWindowOpen); + +function onWindowClose(chromeWindow) { + if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window. + getTabContainers(chromeWindow).forEach(function (container) { + observer.ignore(container); + }); +} +windowObserver.on("close", onWindowClose); + + +// Currently gecko does not dispatches "TabSelect" events when different +// window gets activated. To work around this limitation we emulate "select" +// event for this case. +windowObserver.on("activate", function onWindowActivate(chromeWindow) { + if (!isBrowser(chromeWindow)) return; // Ignore if it's not a browser window. + observer._emit("select", getActiveTab(chromeWindow)); +}); + +// We should synchronize state, since probably we already have at least one +// window open. +for each (let window in browserWindowIterator()) onWindowOpen(window); + +exports.observer = observer; diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js new file mode 100644 index 0000000..246834f --- /dev/null +++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/tab.js @@ -0,0 +1,264 @@ +/* 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 { Ci } = require('chrome'); +const { Trait } = require("../traits"); +const { EventEmitter } = require("../events"); +const { validateOptions } = require("../api-utils"); +const { defer } = require("../functional"); +const { EVENTS } = require("./events"); +const { getThumbnailURIForWindow } = require("../utils/thumbnail"); +const { getFaviconURIForLocation } = require("../utils/data"); + + + +// Array of the inner instances of all the wrapped tabs. +const TABS = []; + +/** + * Trait used to create tab wrappers. + */ +const TabTrait = Trait.compose(EventEmitter, { + on: Trait.required, + _emit: Trait.required, + /** + * Tab DOM element that is being wrapped. + */ + _tab: null, + /** + * Window wrapper whose tab this object represents. + */ + window: null, + constructor: function Tab(options) { + this._onReady = this._onReady.bind(this); + this._tab = options.tab; + let window = this.window = options.window; + // Setting event listener if was passed. + for each (let type in EVENTS) { + let listener = options[type.listener]; + if (listener) + this.on(type.name, options[type.listener]); + if ('ready' != type.name) // window spreads this event. + window.tabs.on(type.name, this._onEvent.bind(this, type.name)); + } + + this.on(EVENTS.close.name, this.destroy.bind(this)); + this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true); + + if (options.isPinned) + this.pin(); + + // Since we will have to identify tabs by a DOM elements facade function + // is used as constructor that collects all the instances and makes sure + // that they more then one wrapper is not created per tab. + return this; + }, + destroy: function destroy() { + this._removeAllListeners(); + this._browser.removeEventListener(EVENTS.ready.dom, this._onReady, + true); + }, + + /** + * Internal listener that emits public event 'ready' when the page of this + * tab is loaded. + */ + _onReady: function _onReady(event) { + // IFrames events will bubble so we need to ignore those. + if (event.target == this._contentDocument) + this._emit(EVENTS.ready.name, this._public); + }, + /** + * Internal tab event router. Window will emit tab related events for all it's + * tabs, this listener will propagate all the events for this tab to it's + * listeners. + */ + _onEvent: function _onEvent(type, tab) { + if (tab == this._public) + this._emit(type, tab); + }, + /** + * Browser DOM element where page of this tab is currently loaded. + */ + get _browser() this._window.gBrowser.getBrowserForTab(this._tab), + /** + * Window DOM element containing this tab. + */ + get _window() this._tab.ownerDocument.defaultView, + /** + * Document object of the page that is currently loaded in this tab. + */ + get _contentDocument() this._browser.contentDocument, + /** + * Window object of the page that is currently loaded in this tab. + */ + get _contentWindow() this._browser.contentWindow, + + /** + * The title of the page currently loaded in the tab. + * Changing this property changes an actual title. + * @type {String} + */ + get title() this._contentDocument.title, + set title(value) this._contentDocument.title = String(value), + /** + * Location of the page currently loaded in this tab. + * Changing this property will loads page under under the specified location. + * @type {String} + */ + get url() String(this._browser.currentURI.spec), + set url(value) this._changeLocation(String(value)), + // "TabOpen" event is fired when it's still "about:blank" is loaded in the + // changing `location` property of the `contentDocument` has no effect since + // seems to be either ignored or overridden by internal listener, there for + // location change is enqueued for the next turn of event loop. + _changeLocation: defer(function(url) this._browser.loadURI(url)), + /** + * URI of the favicon for the page currently loaded in this tab. + * @type {String} + */ + get favicon() getFaviconURIForLocation(this.url), + /** + * The CSS style for the tab + */ + get style() null, // TODO + /** + * The index of the tab relative to other tabs in the application window. + * Changing this property will change order of the actual position of the tab. + * @type {Number} + */ + get index() + this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument), + set index(value) this._window.gBrowser.moveTabTo(this._tab, value), + /** + * Thumbnail data URI of the page currently loaded in this tab. + * @type {String} + */ + getThumbnail: function getThumbnail() + getThumbnailURIForWindow(this._contentWindow), + /** + * Whether or not tab is pinned (Is an app-tab). + * @type {Boolean} + */ + get isPinned() this._tab.pinned, + pin: function pin() { + this._window.gBrowser.pinTab(this._tab); + }, + unpin: function unpin() { + this._window.gBrowser.unpinTab(this._tab); + }, + + /** + * Create a worker for this tab, first argument is options given to Worker. + * @type {Worker} + */ + attach: function attach(options) { + let { Worker } = require("../content/worker"); + options.window = this._contentWindow; + let worker = Worker(options); + worker.once("detach", function detach() { + worker.destroy(); + }); + return worker; + }, + + /** + * Make this tab active. + * Please note: That this function is called synchronous since in E10S that + * will be the case. Besides this function is called from a constructor where + * we would like to return instance before firing a 'TabActivated' event. + */ + activate: defer(function activate() { + if (this._window) // Ignore if window is closed by the time this is invoked. + this._window.gBrowser.selectedTab = this._tab; + }), + /** + * Close the tab + */ + close: function close(callback) { + if (callback) + this.once(EVENTS.close.name, callback); + this._window.gBrowser.removeTab(this._tab); + }, + /** + * Reload the tab + */ + reload: function reload() { + this._window.gBrowser.reloadTab(this._tab); + } +}); + +function Tab(options) { + let chromeTab = options.tab; + for each (let tab in TABS) { + if (chromeTab == tab._tab) + return tab._public; + } + let tab = TabTrait(options); + TABS.push(tab); + return tab._public; +} +Tab.prototype = TabTrait.prototype; +exports.Tab = Tab; + +function Options(options) { + if ("string" === typeof options) + options = { url: options }; + + return validateOptions(options, { + url: { is: ["string"] }, + inBackground: { is: ["undefined", "boolean"] }, + isPinned: { is: ["undefined", "boolean"] }, + onOpen: { is: ["undefined", "function"] }, + onClose: { is: ["undefined", "function"] }, + onReady: { is: ["undefined", "function"] }, + onActivate: { is: ["undefined", "function"] }, + onDeactivate: { is: ["undefined", "function"] } + }); +} +exports.Options = Options; + + +exports.getTabForWindow = function (win) { + // Get browser window + let topWindow = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + if (!topWindow.gBrowser) return null; + + // Get top window object, in case we are in a content iframe + let topContentWindow; + try { + topContentWindow = win.top; + } catch(e) { + // It may throw if win is not a valid content window + return null; + } + + function getWindowID(obj) { + return obj.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID; + } + + // Search for related Tab + let topWindowId = getWindowID(topContentWindow); + for (let i = 0; i < topWindow.gBrowser.browsers.length; i++) { + let w = topWindow.gBrowser.browsers[i].contentWindow; + if (getWindowID(w) == topWindowId) { + return Tab({ + // TODO: api-utils should not depend on addon-kit! + window: require("addon-kit/windows").BrowserWindow({ window: topWindow }), + tab: topWindow.gBrowser.tabs[i] + }); + } + } + + // We were unable to find the related tab! + return null; +} diff --git a/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js new file mode 100644 index 0000000..1c411dc --- /dev/null +++ b/tools/addon-sdk-1.7/packages/api-utils/lib/tabs/utils.js @@ -0,0 +1,59 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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"; + +function getTabContainer(tabBrowser) { + return tabBrowser.tabContainer; +} +exports.getTabContainer = getTabContainer; + +function getTabBrowsers(window) { + return Array.slice(window.document.getElementsByTagName("tabbrowser")); +} +exports.getTabBrowsers = getTabBrowsers; + +function getTabContainers(window) { + return getTabBrowsers(window).map(getTabContainer); +} +exports.getTabContainers = getTabContainers; + +function getTabs(window) { + return getTabContainers(window).reduce(function (tabs, container) { + tabs.push.apply(tabs, container.children); + return tabs; + }, []); +} +exports.getTabs = getTabs; + +function getActiveTab(window) { + return window.gBrowser.selectedTab; +} +exports.getActiveTab = getActiveTab; + +function getOwnerWindow(tab) { + return tab.ownerDocument.defaultView; +} +exports.getOwnerWindow = getOwnerWindow; + +function openTab(window, url) { + return window.gBrowser.addTab(url); +} +exports.openTab = openTab; + +function isTabOpen(tab) { + return !!tab.linkedBrowser; +} +exports.isTabOpen = isTabOpen; + +function closeTab(tab) { + return getOwnerWindow(tab).gBrowser.removeTab(tab); +} +exports.closeTab = closeTab; + +function activateTab(tab) { + getOwnerWindow(tab).gBrowser.selectedTab = tab; +} +exports.activateTab = activateTab; |