Move Add-on SDK source to toolkit/jetpack

This commit is contained in:
NTD 2018-02-09 06:46:43 -05:00 • committed by Roy Tam
commit 23b67db438
993 changed files with 484 additions and 100663 deletions

View file

@ -0,0 +1,34 @@
/* 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 { validateOptions } = require("../deprecated/api-utils");
const { data } = require("../self");
function Options(options) {
if ('string' === typeof options)
options = { url: options };
return validateOptions(options, {
url: {
is: ["string"],
map: (v) => v ? data.url(v) : v
},
inBackground: {
map: Boolean,
is: ["undefined", "boolean"]
},
isPinned: { is: ["undefined", "boolean"] },
isPrivate: { is: ["undefined", "boolean"] },
inNewWindow: { is: ["undefined", "boolean"] },
onOpen: { is: ["undefined", "function"] },
onClose: { is: ["undefined", "function"] },
onReady: { is: ["undefined", "function"] },
onLoad: { is: ["undefined", "function"] },
onPageShow: { is: ["undefined", "function"] },
onActivate: { is: ["undefined", "function"] },
onDeactivate: { is: ["undefined", "function"] }
});
}
exports.Options = Options;

View file

@ -0,0 +1,39 @@
/* 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 ON_PREFIX = "on";
const TAB_PREFIX = "Tab";
const EVENTS = {
ready: "DOMContentLoaded",
load: "load", // Used for non-HTML content
pageshow: "pageshow", // Used for cached content
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: createListenerName(name),
dom: EVENTS[name]
}
});
function createListenerName (name) {
if (name === 'pageshow')
return 'onPageShow';
else
return ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1);
}

View file

@ -0,0 +1,22 @@
/* 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'
};
// NOTE: This file should only export Tab instances
const { getTabForBrowser: getRawTabForBrowser } = require('./utils');
const { modelFor } = require('../model/core');
exports.getTabForRawTab = modelFor;
function getTabForBrowser(browser) {
return modelFor(getRawTabForBrowser(browser)) || null;
}
exports.getTabForBrowser = getTabForBrowser;

View file

@ -0,0 +1,10 @@
/* 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';
var { ns } = require('../core/namespace');
exports.tabsNS = ns();
exports.tabNS = ns();
exports.rawTabNS = ns();

View file

@ -0,0 +1,113 @@
/* 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 { EventTarget } = require("../event/target");
const { emit } = require("../event/core");
const { DOMEventAssembler } = require("../deprecated/events/assembler");
const { Class } = require("../core/heritage");
const { getActiveTab, getTabs } = require("./utils");
const { browserWindowIterator } = require("../deprecated/window-utils");
const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
const { observer: windowObserver } = require("../windows/observer");
const { when } = require("../system/unload");
const EVENTS = {
"TabOpen": "open",
"TabClose": "close",
"TabSelect": "select",
"TabMove": "move",
"TabPinned": "pinned",
"TabUnpinned": "unpinned"
};
const selectedTab = Symbol("observer/state/selectedTab");
// Event emitter objects used to register listeners and emit events on them
// when they occur.
const Observer = Class({
implements: [EventTarget, DOMEventAssembler],
initialize() {
this[selectedTab] = null;
// Currently Gecko does not dispatch 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.
this.on("select", tab => {
const selected = this[selectedTab];
if (selected !== tab) {
if (selected) {
emit(this, 'deactivate', selected);
}
if (tab) {
this[selectedTab] = tab;
emit(this, 'activate', this[selectedTab]);
}
}
});
// We also observe opening / closing windows in order to add / remove it's
// containers to the observed list.
windowObserver.on("open", chromeWindow => {
if (isBrowser(chromeWindow)) {
this.observe(chromeWindow);
}
});
windowObserver.on("close", chromeWindow => {
if (isBrowser(chromeWindow)) {
// Bug 751546: Emit `deactivate` event on window close immediatly
// Otherwise we are going to face "dead object" exception on `select` event
if (getActiveTab(chromeWindow) === this[selectedTab]) {
emit(this, "deactivate", this[selectedTab]);
this[selectedTab] = null;
}
this.ignore(chromeWindow);
}
});
// 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", chromeWindow => {
if (isBrowser(chromeWindow)) {
emit(this, "select", getActiveTab(chromeWindow));
}
});
// We should synchronize state, since probably we already have at least one
// window open.
for (let chromeWindow of browserWindowIterator()) {
this.observe(chromeWindow);
}
when(_ => {
// Don't dispatch a deactivate event during unload.
this[selectedTab] = null;
});
},
/**
* 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) {
emit(this, EVENTS[event.type], event.target, event);
}
});
exports.observer = new Observer();

View file

@ -0,0 +1,249 @@
/* 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 { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage');
const { tabNS, rawTabNS } = require('./namespace');
const { EventTarget } = require('../event/target');
const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
getTabContentWindow, getTabForBrowser, setTabURL, getOwnerWindow,
getTabContentDocument, getTabContentType, getTabId, isTab } = require('./utils');
const { emit } = require('../event/core');
const { isPrivate } = require('../private-browsing/utils');
const { isWindowPrivate } = require('../window/utils');
const { when: unload } = require('../system/unload');
const { BLANK } = require('../content/thumbnail');
const { viewFor } = require('../view/core');
const { EVENTS } = require('./events');
const { modelFor } = require('../model/core');
const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
const Tab = Class({
extends: EventTarget,
initialize: function initialize(options) {
options = options.tab ? options : { tab: options };
let tab = options.tab;
EventTarget.prototype.initialize.call(this, options);
let tabInternals = tabNS(this);
rawTabNS(tab).tab = this;
let window = tabInternals.window = options.window || getOwnerWindow(tab);
tabInternals.tab = tab;
// TabReady
let onReady = tabInternals.onReady = onTabReady.bind(this);
tab.browser.addEventListener(EVENTS.ready.dom, onReady, false);
// TabPageShow
let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this);
tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false);
// TabLoad
let onLoad = tabInternals.onLoad = onTabLoad.bind(this);
tab.browser.addEventListener(EVENTS.load.dom, onLoad, true);
// TabClose
let onClose = tabInternals.onClose = onTabClose.bind(this);
window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onClose, false);
unload(cleanupTab.bind(null, this));
},
/**
* The title of the page currently loaded in the tab.
* Changing this property changes an actual title.
* @type {String}
*/
get title() {
return getTabTitle(tabNS(this).tab);
},
set title(title) {
setTabTitle(tabNS(this).tab, title);
},
/**
* Location of the page currently loaded in this tab.
* Changing this property will loads page under under the specified location.
* @type {String}
*/
get url() {
return tabNS(this).closed ? undefined : getTabURL(tabNS(this).tab);
},
set url(url) {
setTabURL(tabNS(this).tab, url);
},
getThumbnail: function() {
// TODO: implement!
console.error(ERR_FENNEC_MSG);
// return 80x45 blank default
return BLANK;
},
/**
* tab's document readyState, or 'uninitialized' if it doesn't even exist yet.
*/
get readyState() {
let doc = getTabContentDocument(tabNS(this).tab);
return doc && doc.readyState || 'uninitialized';
},
get id() {
return getTabId(tabNS(this).tab);
},
/**
* 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() {
if (tabNS(this).closed) return undefined;
let tabs = tabNS(this).window.BrowserApp.tabs;
let tab = tabNS(this).tab;
for (var i = tabs.length; i >= 0; i--) {
if (tabs[i] === tab)
return i;
}
return null;
},
set index(value) {
console.error(ERR_FENNEC_MSG); // TODO
},
/**
* Whether or not tab is pinned (Is an app-tab).
* @type {Boolean}
*/
get isPinned() {
console.error(ERR_FENNEC_MSG); // TODO
return false; // TODO
},
pin: function pin() {
console.error(ERR_FENNEC_MSG); // TODO
},
unpin: function unpin() {
console.error(ERR_FENNEC_MSG); // TODO
},
/**
* Returns the MIME type that the document loaded in the tab is being
* rendered as.
* @type {String}
*/
get contentType() {
return getTabContentType(tabNS(this).tab);
},
/**
* Create a worker for this tab, first argument is options given to Worker.
* @type {Worker}
*/
attach: function attach(options) {
// BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
// TODO: fix this circular dependency
let { Worker } = require('./worker');
return Worker(options, getTabContentWindow(tabNS(this).tab));
},
/**
* Make this tab active.
*/
activate: function activate() {
activateTab(tabNS(this).tab, tabNS(this).window);
},
/**
* Close the tab
*/
close: function close(callback) {
let tab = this;
this.once(EVENTS.close.name, function () {
tabNS(tab).closed = true;
if (callback) callback();
});
closeTab(tabNS(this).tab);
},
/**
* Reload the tab
*/
reload: function reload() {
tabNS(this).tab.browser.reload();
}
});
exports.Tab = Tab;
// Implement `viewFor` polymorphic function for the Tab
// instances.
viewFor.define(Tab, x => tabNS(x).tab);
function cleanupTab(tab) {
let tabInternals = tabNS(tab);
if (!tabInternals.tab)
return;
if (tabInternals.tab.browser) {
tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false);
tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow, false);
tabInternals.tab.browser.removeEventListener(EVENTS.load.dom, tabInternals.onLoad, true);
}
tabInternals.onReady = null;
tabInternals.onPageShow = null;
tabInternals.onLoad = null;
tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false);
tabInternals.onClose = null;
rawTabNS(tabInternals.tab).tab = null;
tabInternals.tab = null;
tabInternals.window = null;
}
function onTabReady(event) {
let win = event.target.defaultView;
// ignore frames
if (win === win.top) {
emit(this, 'ready', this);
}
}
function onTabLoad (event) {
let win = event.target.defaultView;
// ignore frames
if (win === win.top) {
emit(this, 'load', this);
}
}
function onTabPageShow(event) {
let win = event.target.defaultView;
if (win === win.top)
emit(this, 'pageshow', this, event.persisted);
}
// TabClose
function onTabClose(event) {
let rawTab = getTabForBrowser(event.target);
if (tabNS(this).tab !== rawTab)
return;
emit(this, EVENTS.close.name, this);
cleanupTab(this);
};
isPrivate.implement(Tab, tab => {
return isWindowPrivate(getTabContentWindow(tabNS(tab).tab));
});
// Implement `modelFor` function for the Tab instances.
modelFor.when(isTab, rawTab => {
return rawTabNS(rawTab).tab;
});

View file

@ -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);
});

View file

@ -0,0 +1,24 @@
/* 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 { getTargetWindow } = require("../content/mod");
const { getTabContentWindow, isTab } = require("./utils");
const { viewFor } = require("../view/core");
if (require('../system/xul-app').name == 'Fennec') {
module.exports = require('./tab-fennec');
}
else {
module.exports = require('./tab-firefox');
}
getTargetWindow.when(isTab, tab => getTabContentWindow(tab));
getTargetWindow.when(x => x instanceof module.exports.Tab,
tab => getTabContentWindow(viewFor(tab)));

View file

@ -0,0 +1,135 @@
/* 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 { Tab, tabEvents } = require('./tab');
const { EventTarget } = require('../event/target');
const { emit, setListeners } = require('../event/core');
const { pipe } = require('../event/utils');
const { observer: windowObserver } = require('../windows/observer');
const { List, addListItem, removeListItem } = require('../util/list');
const { modelFor } = require('../model/core');
const { viewFor } = require('../view/core');
const { getTabs, getSelectedTab } = require('./utils');
const { getMostRecentBrowserWindow, isBrowser } = require('../window/utils');
const { Options } = require('./common');
const { isPrivate } = require('../private-browsing');
const { ignoreWindow, isWindowPBSupported } = require('../private-browsing/utils')
const { isPrivateBrowsingSupported } = require('sdk/self');
const supportPrivateTabs = isPrivateBrowsingSupported && isWindowPBSupported;
const Tabs = Class({
implements: [EventTarget],
extends: List,
initialize: function() {
List.prototype.initialize.call(this);
// We must do the list manipulation here where the object is extensible
this.on("open", tab => {
addListItem(this, tab);
});
this.on("close", tab => {
removeListItem(this, tab);
});
},
get activeTab() {
let activeDomWin = getMostRecentBrowserWindow();
if (!activeDomWin)
return null;
return modelFor(getSelectedTab(activeDomWin));
},
open: function(options) {
options = Options(options);
// TODO: Remove the dependency on the windows module: bug 792670
let windows = require('../windows').browserWindows;
let activeWindow = windows.activeWindow;
let privateState = supportPrivateTabs && options.isPrivate;
// When no isPrivate option was passed use the private state of the active
// window
if (activeWindow && privateState === undefined)
privateState = isPrivate(activeWindow);
function getWindow(privateState) {
for (let window of windows) {
if (privateState === isPrivate(window)) {
return window;
}
}
return null;
}
function openNewWindowWithTab() {
windows.open({
url: options.url,
isPrivate: privateState,
onOpen: function(newWindow) {
let tab = newWindow.tabs[0];
setListeners(tab, options);
if (options.isPinned)
tab.pin();
// We don't emit the open event for the first tab in a new window so
// do it now the listeners are attached
emit(tab, "open", tab);
}
});
}
if (options.inNewWindow)
return openNewWindowWithTab();
// if the active window is in the state that we need then use it
if (activeWindow && (privateState === isPrivate(activeWindow)))
return activeWindow.tabs.open(options);
// find a window in the state that we need
let window = getWindow(privateState);
if (window)
return window.tabs.open(options);
return openNewWindowWithTab();
}
});
const allTabs = new Tabs();
// Export a new object with allTabs as the prototype, otherwise allTabs becomes
// frozen and addListItem and removeListItem don't work correctly.
module.exports = Object.create(allTabs);
pipe(tabEvents, module.exports);
function addWindowTab(window, tabElement) {
let tab = new Tab(tabElement);
if (window)
addListItem(window.tabs, tab);
addListItem(allTabs, tab);
emit(allTabs, "open", tab);
}
// Find tabs in already open windows
for (let tabElement of getTabs())
addWindowTab(null, tabElement);
// Detect tabs in new windows
windowObserver.on('open', domWindow => {
if (!isBrowser(domWindow) || ignoreWindow(domWindow))
return;
let window = null;
try {
modelFor(domWindow);
}
catch (e) { }
for (let tabElement of getTabs(domWindow)) {
addWindowTab(window, tabElement);
}
});

View file

@ -0,0 +1,370 @@
/* 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'
};
// NOTE: This file should only deal with xul/native tabs
const { Ci, Cu } = require('chrome');
const { defer } = require("../lang/functional");
const { windows, isBrowser } = require('../window/utils');
const { isPrivateBrowsingSupported } = require('../self');
const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
// Bug 834961: ignore private windows when they are not supported
function getWindows() {
return windows(null, { includePrivate: isPrivateBrowsingSupported });
}
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Define predicate functions that can be used to detech weather
// we deal with fennec tabs or firefox tabs.
// Predicate to detect whether tab is XUL "Tab" node.
const isXULTab = tab =>
tab instanceof Ci.nsIDOMNode &&
tab.nodeName === "tab" &&
tab.namespaceURI === XUL_NS;
exports.isXULTab = isXULTab;
// Predicate to detecet whether given tab is a fettec tab.
// Unfortunately we have to guess via duck typinng of:
// http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583
const isFennecTab = tab =>
tab &&
tab.QueryInterface &&
Ci.nsIBrowserTab &&
tab.QueryInterface(Ci.nsIBrowserTab) === tab;
exports.isFennecTab = isFennecTab;
const isTab = x => isXULTab(x) || isFennecTab(x);
exports.isTab = isTab;
function activateTab(tab, window) {
let gBrowser = getTabBrowserForTab(tab);
// normal case
if (gBrowser) {
gBrowser.selectedTab = tab;
}
// fennec ?
else if (window && window.BrowserApp) {
window.BrowserApp.selectTab(tab);
}
return null;
}
exports.activateTab = activateTab;
function getTabBrowser(window) {
// bug 1009938 - may be null in SeaMonkey
return window.gBrowser || window.getBrowser();
}
exports.getTabBrowser = getTabBrowser;
function getTabContainer(window) {
return getTabBrowser(window).tabContainer;
}
exports.getTabContainer = getTabContainer;
/**
* Returns the tabs for the `window` if given, or the tabs
* across all the browser's windows otherwise.
*
* @param {nsIWindow} [window]
* A reference to a window
*
* @returns {Array} an array of Tab objects
*/
function getTabs(window) {
if (arguments.length === 0) {
return getWindows().
filter(isBrowser).
reduce((tabs, window) => tabs.concat(getTabs(window)), []);
}
// fennec
if (window.BrowserApp)
return window.BrowserApp.tabs;
// firefox - default
return Array.filter(getTabContainer(window).children, t => !t.closing);
}
exports.getTabs = getTabs;
function getActiveTab(window) {
return getSelectedTab(window);
}
exports.getActiveTab = getActiveTab;
function getOwnerWindow(tab) {
// normal case
if (tab.ownerDocument)
return tab.ownerDocument.defaultView;
// try fennec case
return getWindowHoldingTab(tab);
}
exports.getOwnerWindow = getOwnerWindow;
// fennec
function getWindowHoldingTab(rawTab) {
for (let window of getWindows()) {
// this function may be called when not using fennec,
// but BrowserApp is only defined on Fennec
if (!window.BrowserApp)
continue;
for (let tab of window.BrowserApp.tabs) {
if (tab === rawTab)
return window;
}
}
return null;
}
function openTab(window, url, options) {
options = options || {};
// fennec?
if (window.BrowserApp) {
return window.BrowserApp.addTab(url, {
selected: options.inBackground ? false : true,
pinned: options.isPinned || false,
isPrivate: options.isPrivate || false,
parentId: window.BrowserApp.selectedTab.id
});
}
// firefox
let newTab = window.gBrowser.addTab(url);
if (!options.inBackground) {
activateTab(newTab);
}
return newTab;
};
exports.openTab = openTab;
function isTabOpen(tab) {
// try normal case then fennec case
return !!((tab.linkedBrowser) || getWindowHoldingTab(tab));
}
exports.isTabOpen = isTabOpen;
function closeTab(tab) {
let gBrowser = getTabBrowserForTab(tab);
// normal case?
if (gBrowser) {
// Bug 699450: the tab may already have been detached
if (!tab.parentNode)
return;
return gBrowser.removeTab(tab);
}
let window = getWindowHoldingTab(tab);
// fennec?
if (window && window.BrowserApp) {
// Bug 699450: the tab may already have been detached
if (!tab.browser)
return;
return window.BrowserApp.closeTab(tab);
}
return null;
}
exports.closeTab = closeTab;
function getURI(tab) {
if (tab.browser) // fennec
return tab.browser.currentURI.spec;
return tab.linkedBrowser.currentURI.spec;
}
exports.getURI = getURI;
function getTabBrowserForTab(tab) {
let outerWin = getOwnerWindow(tab);
if (outerWin)
return getOwnerWindow(tab).gBrowser;
return null;
}
exports.getTabBrowserForTab = getTabBrowserForTab;
function getBrowserForTab(tab) {
if (tab.browser) // fennec
return tab.browser;
return tab.linkedBrowser;
}
exports.getBrowserForTab = getBrowserForTab;
function getTabId(tab) {
if (tab.browser) // fennec
return tab.id
return String.split(tab.linkedPanel, 'panel').pop();
}
exports.getTabId = getTabId;
function getTabForId(id) {
return getTabs().find(tab => getTabId(tab) === id) || null;
}
exports.getTabForId = getTabForId;
function getTabTitle(tab) {
return getBrowserForTab(tab).contentTitle || tab.label || "";
}
exports.getTabTitle = getTabTitle;
function setTabTitle(tab, title) {
title = String(title);
if (tab.browser) {
// Fennec
tab.browser.contentDocument.title = title;
}
else {
let browser = getBrowserForTab(tab);
// Note that we aren't actually setting the document title in e10s, just
// the title the browser thinks the content has
if (browser.isRemoteBrowser)
browser._contentTitle = title;
else
browser.contentDocument.title = title;
}
tab.label = String(title);
}
exports.setTabTitle = setTabTitle;
function getTabContentDocument(tab) {
return getBrowserForTab(tab).contentDocument;
}
exports.getTabContentDocument = getTabContentDocument;
function getTabContentWindow(tab) {
return getBrowserForTab(tab).contentWindow;
}
exports.getTabContentWindow = getTabContentWindow;
/**
* Returns all tabs' content windows across all the browsers' windows
*/
function getAllTabContentWindows() {
return getTabs().map(getTabContentWindow);
}
exports.getAllTabContentWindows = getAllTabContentWindows;
// gets the tab containing the provided window
function getTabForContentWindow(window) {
return getTabs().find(tab => getTabContentWindow(tab) === window.top) || null;
}
exports.getTabForContentWindow = getTabForContentWindow;
// only sdk/selection.js is relying on shims
function getTabForContentWindowNoShim(window) {
function getTabContentWindowNoShim(tab) {
let browser = getBrowserForTab(tab);
return ShimWaiver.getProperty(browser, "contentWindow");
}
return getTabs().find(tab => getTabContentWindowNoShim(tab) === window.top) || null;
}
exports.getTabForContentWindowNoShim = getTabForContentWindowNoShim;
function getTabURL(tab) {
return String(getBrowserForTab(tab).currentURI.spec);
}
exports.getTabURL = getTabURL;
function setTabURL(tab, url) {
let browser = getBrowserForTab(tab);
browser.loadURI(String(url));
}
// "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.
exports.setTabURL = defer(setTabURL);
function getTabContentType(tab) {
return getBrowserForTab(tab).contentDocument.contentType;
}
exports.getTabContentType = getTabContentType;
function getSelectedTab(window) {
if (window.BrowserApp) // fennec?
return window.BrowserApp.selectedTab;
if (window.gBrowser)
return window.gBrowser.selectedTab;
return null;
}
exports.getSelectedTab = getSelectedTab;
function getTabForBrowser(browser) {
for (let window of getWindows()) {
// this function may be called when not using fennec
if (!window.BrowserApp)
continue;
for (let tab of window.BrowserApp.tabs) {
if (tab.browser === browser)
return tab;
}
}
let tabbrowser = browser.getTabBrowser && browser.getTabBrowser()
return !!tabbrowser && tabbrowser.getTabForBrowser(browser);
}
exports.getTabForBrowser = getTabForBrowser;
function pin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.pinTab(tab);
}
exports.pin = pin;
function unpin(tab) {
let gBrowser = getTabBrowserForTab(tab);
// TODO: Implement Fennec support
if (gBrowser) gBrowser.unpinTab(tab);
}
exports.unpin = unpin;
function isPinned(tab) {
return !!tab.pinned;
}
exports.isPinned = isPinned;
function reload(tab) {
getBrowserForTab(tab).reload();
}
exports.reload = reload
function getIndex(tab) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) {
return tab._tPos;
}
// Fennec
else {
let window = getWindowHoldingTab(tab)
let tabs = window.BrowserApp.tabs;
for (let i = tabs.length; i >= 0; i--)
if (tabs[i] === tab) return i;
}
}
exports.getIndex = getIndex;
function move(tab, index) {
let gBrowser = getTabBrowserForTab(tab);
// Firefox
if (gBrowser) gBrowser.moveTabTo(tab, index);
// TODO: Implement fennec support
}
exports.move = move;

View file

@ -0,0 +1,17 @@
/* 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 ContentWorker = require('../content/worker').Worker;
function Worker(options, window) {
options.window = window;
let worker = ContentWorker(options);
worker.once("detach", function detach() {
worker.destroy();
});
return worker;
}
exports.Worker = Worker;