summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/tabs/tab-firefox.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/tabs/tab-firefox.js')
-rw-r--r--toolkit/jetpack/sdk/tabs/tab-firefox.js353
1 files changed, 353 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/tabs/tab-firefox.js b/toolkit/jetpack/sdk/tabs/tab-firefox.js
new file mode 100644
index 000000000..f1da92379
--- /dev/null
+++ b/toolkit/jetpack/sdk/tabs/tab-firefox.js
@@ -0,0 +1,353 @@
+/* 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 { Class } = require('../core/heritage');
+const { observer } = require('./observer');
+const { observer: windowObserver } = require('../windows/observer');
+const { addListItem, removeListItem } = require('../util/list');
+const { viewFor } = require('../view/core');
+const { modelFor } = require('../model/core');
+const { emit, setListeners } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { getBrowserForTab, setTabURL, getTabId, getTabURL, getTabForBrowser,
+ getTabs, getTabTitle, setTabTitle, getIndex, closeTab, reload, move,
+ activateTab, pin, unpin, isTab } = require('./utils');
+const { isBrowser, getInnerId, isWindowPrivate } = require('../window/utils');
+const { getThumbnailURIForWindow, BLANK } = require("../content/thumbnail");
+const { when } = require('../system/unload');
+const { ignoreWindow, isPrivate } = require('../private-browsing/utils')
+const { defer } = require('../lang/functional');
+const { getURL } = require('../url/utils');
+const { frames, remoteRequire } = require('../remote/parent');
+remoteRequire('sdk/content/tab-events');
+
+const modelsFor = new WeakMap();
+const viewsFor = new WeakMap();
+const destroyed = new WeakMap();
+
+const tabEvents = {};
+exports.tabEvents = tabEvents;
+
+function browser(tab) {
+ return getBrowserForTab(viewsFor.get(tab));
+}
+
+function isDestroyed(tab) {
+ return destroyed.has(tab);
+}
+
+function isClosed(tab) {
+ if (!viewsFor.has(tab))
+ return true;
+ return viewsFor.get(tab).closing;
+}
+
+// private tab attribute where the remote cached value is stored
+const remoteReadyStateCached = Symbol("remoteReadyStateCached");
+
+const Tab = Class({
+ implements: [EventTarget],
+ initialize: function(tabElement, options = null) {
+ modelsFor.set(tabElement, this);
+ viewsFor.set(this, tabElement);
+
+ if (options) {
+ EventTarget.prototype.initialize.call(this, options);
+
+ if (options.isPinned)
+ this.pin();
+
+ // Note that activate is defered and so will run after any open event
+ // is sent out
+ if (!options.inBackground)
+ this.activate();
+ }
+
+ getURL.implement(this, tab => tab.url);
+ isPrivate.implement(this, tab => {
+ return isWindowPrivate(viewsFor.get(tab).ownerDocument.defaultView);
+ });
+ },
+
+ get id() {
+ return isDestroyed(this) ? undefined : getTabId(viewsFor.get(this));
+ },
+
+ get title() {
+ return isDestroyed(this) ? undefined : getTabTitle(viewsFor.get(this));
+ },
+
+ set title(val) {
+ if (isDestroyed(this))
+ return;
+
+ setTabTitle(viewsFor.get(this), val);
+ },
+
+ get url() {
+ return isDestroyed(this) ? undefined : getTabURL(viewsFor.get(this));
+ },
+
+ set url(val) {
+ if (isDestroyed(this))
+ return;
+
+ setTabURL(viewsFor.get(this), val);
+ },
+
+ get contentType() {
+ return isDestroyed(this) ? undefined : browser(this).documentContentType;
+ },
+
+ get index() {
+ return isDestroyed(this) ? undefined : getIndex(viewsFor.get(this));
+ },
+
+ set index(val) {
+ if (isDestroyed(this))
+ return;
+
+ move(viewsFor.get(this), val);
+ },
+
+ get isPinned() {
+ return isDestroyed(this) ? undefined : viewsFor.get(this).pinned;
+ },
+
+ get window() {
+ if (isClosed(this))
+ return undefined;
+
+ // TODO: Remove the dependency on the windows module, see bug 792670
+ require('../windows');
+ let tabElement = viewsFor.get(this);
+ let domWindow = tabElement.ownerDocument.defaultView;
+ return modelFor(domWindow);
+ },
+
+ get readyState() {
+ return isDestroyed(this) ? undefined : this[remoteReadyStateCached] || "uninitialized";
+ },
+
+ pin: function() {
+ if (isDestroyed(this))
+ return;
+
+ pin(viewsFor.get(this));
+ },
+
+ unpin: function() {
+ if (isDestroyed(this))
+ return;
+
+ unpin(viewsFor.get(this));
+ },
+
+ close: function(callback) {
+ let tabElement = viewsFor.get(this);
+
+ if (isDestroyed(this) || !tabElement || !tabElement.parentNode) {
+ if (callback)
+ callback();
+ return;
+ }
+
+ this.once('close', () => {
+ this.destroy();
+ if (callback)
+ callback();
+ });
+
+ closeTab(tabElement);
+ },
+
+ reload: function() {
+ if (isDestroyed(this))
+ return;
+
+ reload(viewsFor.get(this));
+ },
+
+ activate: defer(function() {
+ if (isDestroyed(this))
+ return;
+
+ activateTab(viewsFor.get(this));
+ }),
+
+ getThumbnail: function() {
+ if (isDestroyed(this))
+ return BLANK;
+
+ // TODO: This is unimplemented in e10s: bug 1148601
+ if (browser(this).isRemoteBrowser) {
+ console.error('This method is not supported with E10S');
+ return BLANK;
+ }
+ return getThumbnailURIForWindow(browser(this).contentWindow);
+ },
+
+ attach: function(options) {
+ if (isDestroyed(this))
+ return;
+
+ let { Worker } = require('../content/worker');
+ let { connect, makeChildOptions } = require('../content/utils');
+
+ let worker = Worker(options);
+ worker.once("detach", () => {
+ worker.destroy();
+ });
+
+ let attach = frame => {
+ let childOptions = makeChildOptions(options);
+ frame.port.emit("sdk/tab/attach", childOptions);
+ connect(worker, frame, { id: childOptions.id, url: this.url });
+ };
+
+ // Do this synchronously if possible
+ let frame = frames.getFrameForBrowser(browser(this));
+ if (frame) {
+ attach(frame);
+ }
+ else {
+ let listener = (frame) => {
+ if (frame.frameElement != browser(this))
+ return;
+
+ frames.off("attach", listener);
+ attach(frame);
+ };
+ frames.on("attach", listener);
+ }
+
+ return worker;
+ },
+
+ destroy: function() {
+ if (isDestroyed(this))
+ return;
+
+ destroyed.set(this, true);
+ }
+});
+exports.Tab = Tab;
+
+viewFor.define(Tab, tab => viewsFor.get(tab));
+
+// Returns the high-level window for this DOM window if the windows module has
+// ever been loaded otherwise returns null
+function maybeWindowFor(domWindow) {
+ try {
+ return modelFor(domWindow);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function tabEmit(tab, event, ...args) {
+ // Don't emit events for destroyed tabs
+ if (isDestroyed(tab))
+ return;
+
+ // If the windows module was never loaded this will return null. We don't need
+ // to emit to the window.tabs object in this case as nothing can be listening.
+ let tabElement = viewsFor.get(tab);
+ let window = maybeWindowFor(tabElement.ownerDocument.defaultView);
+ if (window)
+ emit(window.tabs, event, tab, ...args);
+
+ emit(tabEvents, event, tab, ...args);
+ emit(tab, event, tab, ...args);
+}
+
+function windowClosed(domWindow) {
+ if (!isBrowser(domWindow))
+ return;
+
+ for (let tabElement of getTabs(domWindow)) {
+ tabEventListener("close", tabElement);
+ }
+}
+windowObserver.on('close', windowClosed);
+
+// Don't want to send close events after unloaded
+when(_ => {
+ windowObserver.off('close', windowClosed);
+});
+
+// Listen for tabbrowser events
+function tabEventListener(event, tabElement, ...args) {
+ let domWindow = tabElement.ownerDocument.defaultView;
+
+ if (ignoreWindow(domWindow))
+ return;
+
+ // Don't send events for tabs that are already closing
+ if (event != "close" && (tabElement.closing || !tabElement.parentNode))
+ return;
+
+ let tab = modelsFor.get(tabElement);
+ if (!tab)
+ tab = new Tab(tabElement);
+
+ let window = maybeWindowFor(domWindow);
+
+ if (event == "open") {
+ // Note, add to the window tabs first because if this is the first access to
+ // window.tabs it will be prefilling itself with everything from tabs
+ if (window)
+ addListItem(window.tabs, tab);
+ // The tabs module will take care of adding to its internal list
+ }
+ else if (event == "close") {
+ if (window)
+ removeListItem(window.tabs, tab);
+ // The tabs module will take care of removing from its internal list
+ }
+ else if (event == "init" || event == "create" || event == "ready" || event == "load") {
+ // Ignore load events from before browser windows have fully loaded, these
+ // are for about:blank in the initial tab
+ if (isBrowser(domWindow) && !domWindow.gBrowserInit.delayedStartupFinished)
+ return;
+
+ // update the cached remote readyState value
+ let { readyState } = args[0] || {};
+ tab[remoteReadyStateCached] = readyState;
+ }
+
+ if (event == "init") {
+ // Do not emit events for the detected existent tabs, we only need to cache
+ // their current document.readyState value.
+ return;
+ }
+
+ tabEmit(tab, event, ...args);
+
+ // The tab object shouldn't be reachable after closed
+ if (event == "close") {
+ viewsFor.delete(tab);
+ modelsFor.delete(tabElement);
+ }
+}
+observer.on('*', tabEventListener);
+
+// Listen for tab events from content
+frames.port.on('sdk/tab/event', (frame, event, ...args) => {
+ if (!frame.isTab)
+ return;
+
+ let tabElement = getTabForBrowser(frame.frameElement);
+ if (!tabElement)
+ return;
+
+ tabEventListener(event, tabElement, ...args);
+});
+
+// Implement `modelFor` function for the Tab instances..
+modelFor.when(isTab, view => {
+ return modelsFor.get(view);
+});