From ac46df8daea09899ce30dc8fd70986e258c746bf Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 9 Feb 2018 06:46:43 -0500 Subject: Move Add-on SDK source to toolkit/jetpack --- toolkit/jetpack/sdk/windows/fennec.js | 83 +++++++++++ toolkit/jetpack/sdk/windows/firefox.js | 224 +++++++++++++++++++++++++++++ toolkit/jetpack/sdk/windows/observer.js | 53 +++++++ toolkit/jetpack/sdk/windows/tabs-fennec.js | 172 ++++++++++++++++++++++ 4 files changed, 532 insertions(+) create mode 100644 toolkit/jetpack/sdk/windows/fennec.js create mode 100644 toolkit/jetpack/sdk/windows/firefox.js create mode 100644 toolkit/jetpack/sdk/windows/observer.js create mode 100644 toolkit/jetpack/sdk/windows/tabs-fennec.js (limited to 'toolkit/jetpack/sdk/windows') diff --git a/toolkit/jetpack/sdk/windows/fennec.js b/toolkit/jetpack/sdk/windows/fennec.js new file mode 100644 index 000000000..3c3b6c313 --- /dev/null +++ b/toolkit/jetpack/sdk/windows/fennec.js @@ -0,0 +1,83 @@ +/* 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 { BrowserWindow } = require('../window/browser'); +const { WindowTracker } = require('../deprecated/window-utils'); +const { isBrowser, getMostRecentBrowserWindow } = require('../window/utils'); +const { windowNS } = require('../window/namespace'); +const { on, off, once, emit } = require('../event/core'); +const { method } = require('../lang/functional'); +const { EventTarget } = require('../event/target'); +const { List, addListItem } = require('../util/list'); + +const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead'; + +// NOTE: On Fennec there is only one window. + +var BrowserWindows = Class({ + implements: [ List ], + extends: EventTarget, + initialize: function() { + List.prototype.initialize.apply(this); + }, + get activeWindow() { + let window = getMostRecentBrowserWindow(); + return window ? getBrowserWindow({window: window}) : null; + }, + open: function open(options) { + throw new Error(ERR_FENNEC_MSG); + return null; + } +}); +const browserWindows = exports.browserWindows = BrowserWindows(); + + +/** + * Gets a `BrowserWindow` for the given `chromeWindow` if previously + * registered, `null` otherwise. + */ +function getRegisteredWindow(chromeWindow) { + for (let window of browserWindows) { + if (chromeWindow === windowNS(window).window) + return window; + } + + return null; +} + +/** + * Gets a `BrowserWindow` for the provided window options obj + * @params {Object} options + * Options that are passed to the the `BrowserWindow` + * @returns {BrowserWindow} + */ +function getBrowserWindow(options) { + let window = null; + + // if we have a BrowserWindow already then use it + if ('window' in options) + window = getRegisteredWindow(options.window); + if (window) + return window; + + // we don't have a BrowserWindow yet, so create one + window = BrowserWindow(options); + addListItem(browserWindows, window); + return window; +} + +WindowTracker({ + onTrack: function onTrack(chromeWindow) { + if (!isBrowser(chromeWindow)) return; + let window = getBrowserWindow({ window: chromeWindow }); + emit(browserWindows, 'open', window); + }, + onUntrack: function onUntrack(chromeWindow) { + if (!isBrowser(chromeWindow)) return; + let window = getBrowserWindow({ window: chromeWindow }); + emit(browserWindows, 'close', window); + } +}); diff --git a/toolkit/jetpack/sdk/windows/firefox.js b/toolkit/jetpack/sdk/windows/firefox.js new file mode 100644 index 000000000..1eb1d8488 --- /dev/null +++ b/toolkit/jetpack/sdk/windows/firefox.js @@ -0,0 +1,224 @@ +/* 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 { isBrowser, getMostRecentBrowserWindow, windows, open, getInnerId, + getWindowTitle, getToplevelWindow, isFocused, isWindowPrivate } = require('../window/utils'); +const { List, addListItem, removeListItem } = require('../util/list'); +const { viewFor } = require('../view/core'); +const { modelFor } = require('../model/core'); +const { emit, emitOnObject, setListeners } = require('../event/core'); +const { once } = require('../dom/events'); +const { EventTarget } = require('../event/target'); +const { getSelectedTab } = require('../tabs/utils'); +const { Cc, Ci } = require('chrome'); +const { Options } = require('../tabs/common'); +const system = require('../system/events'); +const { ignoreWindow, isPrivate, isWindowPBSupported } = require('../private-browsing/utils'); +const { data, isPrivateBrowsingSupported } = require('../self'); +const { setImmediate } = require('../timers'); + +const supportPrivateWindows = isPrivateBrowsingSupported && isWindowPBSupported; + +const modelsFor = new WeakMap(); +const viewsFor = new WeakMap(); + +const Window = Class({ + implements: [EventTarget], + initialize: function(domWindow) { + modelsFor.set(domWindow, this); + viewsFor.set(this, domWindow); + }, + + get title() { + return getWindowTitle(viewsFor.get(this)); + }, + + activate: function() { + viewsFor.get(this).focus(); + }, + + close: function(callback) { + let domWindow = viewsFor.get(this); + + if (callback) { + // We want to catch the close event immediately after the close events are + // emitted everywhere but without letting the event loop spin. Registering + // for the same events as windowEventListener but afterwards does this + let listener = (event, closedWin) => { + if (event != "close" || closedWin != domWindow) + return; + + observer.off("*", listener); + callback(); + } + + observer.on("*", listener); + } + + domWindow.close(); + } +}); + +const windowTabs = new WeakMap(); + +const BrowserWindow = Class({ + extends: Window, + + get tabs() { + let tabs = windowTabs.get(this); + if (tabs) + return tabs; + + return new WindowTabs(this); + } +}); + +const WindowTabs = Class({ + implements: [EventTarget], + extends: List, + initialize: function(window) { + List.prototype.initialize.call(this); + windowTabs.set(window, this); + viewsFor.set(this, viewsFor.get(window)); + + // Make sure the tabs module has loaded and found all existing tabs + const tabs = require('../tabs'); + + for (let tab of tabs) { + if (tab.window == window) + addListItem(this, tab); + } + }, + + get activeTab() { + return modelFor(getSelectedTab(viewsFor.get(this))); + }, + + open: function(options) { + options = Options(options); + + let domWindow = viewsFor.get(this); + let { Tab } = require('../tabs/tab-firefox'); + + // The capturing listener will see the TabOpen event before + // sdk/tabs/observer giving us time to set up the tab and listeners before + // the real open event is fired + let listener = event => { + new Tab(event.target, options); + }; + + once(domWindow, "TabOpen", listener, true); + domWindow.gBrowser.addTab(options.url); + } +}); + +const BrowserWindows = Class({ + implements: [EventTarget], + extends: List, + initialize: function() { + List.prototype.initialize.call(this); + }, + + get activeWindow() { + let domWindow = getMostRecentBrowserWindow(); + if (ignoreWindow(domWindow)) + return null; + return modelsFor.get(domWindow); + }, + + open: function(options) { + if (typeof options == "string") + options = { url: options }; + + let { url, isPrivate } = options; + if (url) + url = data.url(url); + + let args = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + args.data = url; + + let features = { + chrome: true, + all: true, + dialog: false + }; + features.private = supportPrivateWindows && isPrivate; + + let domWindow = open(null, { + parent: null, + name: "_blank", + features, + args + }) + + let window = makeNewWindow(domWindow, true); + setListeners(window, options); + return window; + } +}); + +const browserWindows = new BrowserWindows(); +exports.browserWindows = browserWindows; + +function windowEmit(window, event, ...args) { + if (window instanceof BrowserWindow && (event == "open" || event == "close")) + emitOnObject(window, event, browserWindows, window, ...args); + else + emit(window, event, window, ...args); + + if (window instanceof BrowserWindow) + emit(browserWindows, event, window, ...args); +} + +function makeNewWindow(domWindow, browserHint = false) { + if (browserHint || isBrowser(domWindow)) + return new BrowserWindow(domWindow); + else + return new Window(domWindow); +} + +for (let domWindow of windows(null, {includePrivate: supportPrivateWindows})) { + let window = makeNewWindow(domWindow); + if (window instanceof BrowserWindow) + addListItem(browserWindows, window); +} + +var windowEventListener = (event, domWindow, ...args) => { + let toplevelWindow = getToplevelWindow(domWindow); + + if (ignoreWindow(toplevelWindow)) + return; + + let window = modelsFor.get(toplevelWindow); + if (!window) + window = makeNewWindow(toplevelWindow); + + if (isBrowser(toplevelWindow)) { + if (event == "open") + addListItem(browserWindows, window); + else if (event == "close") + removeListItem(browserWindows, window); + } + + windowEmit(window, event, ...args); + + // The window object shouldn't be reachable after closed + if (event == "close") { + viewsFor.delete(window); + modelsFor.delete(toplevelWindow); + } +}; +observer.on("*", windowEventListener); + +viewFor.define(BrowserWindow, window => { + return viewsFor.get(window); +}) + +const isBrowserWindow = (x) => x instanceof BrowserWindow; +isPrivate.when(isBrowserWindow, (w) => isWindowPrivate(viewsFor.get(w))); +isFocused.when(isBrowserWindow, (w) => isFocused(viewsFor.get(w))); diff --git a/toolkit/jetpack/sdk/windows/observer.js b/toolkit/jetpack/sdk/windows/observer.js new file mode 100644 index 000000000..5ba2535f1 --- /dev/null +++ b/toolkit/jetpack/sdk/windows/observer.js @@ -0,0 +1,53 @@ +/* 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 { WindowTracker, windowIterator } = require("../deprecated/window-utils"); +const { DOMEventAssembler } = require("../deprecated/events/assembler"); +const { Class } = require("../core/heritage"); +const { Cu } = require("chrome"); + +// Event emitter objects used to register listeners and emit events on them +// when they occur. +const Observer = Class({ + initialize() { + // Using `WindowTracker` to track window events. + WindowTracker({ + onTrack: chromeWindow => { + emit(this, "open", chromeWindow); + this.observe(chromeWindow); + }, + onUntrack: chromeWindow => { + emit(this, "close", chromeWindow); + this.ignore(chromeWindow); + } + }); + }, + implements: [EventTarget, DOMEventAssembler], + /** + * Events that are supported and emitted by the module. + */ + supportedEventsTypes: [ "activate", "deactivate" ], + /** + * 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(event) { + // Ignore events from windows in the child process as they can't be top-level + if (Cu.isCrossProcessWrapper(event.target)) + return; + emit(this, event.type, event.target, event); + } +}); + +exports.observer = new Observer(); diff --git a/toolkit/jetpack/sdk/windows/tabs-fennec.js b/toolkit/jetpack/sdk/windows/tabs-fennec.js new file mode 100644 index 000000000..0ef5ec9f5 --- /dev/null +++ b/toolkit/jetpack/sdk/windows/tabs-fennec.js @@ -0,0 +1,172 @@ +/* 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 } = require('../tabs/tab'); +const { browserWindows } = require('./fennec'); +const { windowNS } = require('../window/namespace'); +const { tabsNS, tabNS } = require('../tabs/namespace'); +const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser, + getTabContentWindow } = require('../tabs/utils'); +const { Options } = require('../tabs/common'); +const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers'); +const { on, once, off, emit } = require('../event/core'); +const { method } = require('../lang/functional'); +const { EVENTS } = require('../tabs/events'); +const { EventTarget } = require('../event/target'); +const { when: unload } = require('../system/unload'); +const { windowIterator } = require('../deprecated/window-utils'); +const { List, addListItem, removeListItem } = require('../util/list'); +const { isPrivateBrowsingSupported, data } = require('../self'); +const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils'); + +const mainWindow = windowNS(browserWindows.activeWindow).window; + +const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec'; + +const supportPrivateTabs = isPrivateBrowsingSupported && isTabPBSupported; + +const Tabs = Class({ + implements: [ List ], + extends: EventTarget, + initialize: function initialize(options) { + let tabsInternals = tabsNS(this); + let window = tabsNS(this).window = options.window || mainWindow; + + EventTarget.prototype.initialize.call(this, options); + List.prototype.initialize.apply(this, getTabs(window).map(Tab)); + + // TabOpen event + window.BrowserApp.deck.addEventListener(EVENTS.open.dom, onTabOpen, false); + + // TabSelect + window.BrowserApp.deck.addEventListener(EVENTS.activate.dom, onTabSelect, false); + }, + get activeTab() { + return getTabForRawTab(getSelectedTab(tabsNS(this).window)); + }, + open: function(options) { + options = Options(options); + let activeWin = browserWindows.activeWindow; + + if (options.isPinned) { + console.error(ERR_FENNEC_MSG); // TODO + } + + let url = options.url ? data.url(options.url) : options.url; + let rawTab = openTab(windowNS(activeWin).window, url, { + inBackground: options.inBackground, + isPrivate: supportPrivateTabs && options.isPrivate + }); + + // by now the tab has been created + let tab = getTabForRawTab(rawTab); + + if (options.onClose) + tab.on('close', options.onClose); + + if (options.onOpen) { + // NOTE: on Fennec this will be true + if (tabNS(tab).opened) + options.onOpen(tab); + + tab.on('open', options.onOpen); + } + + if (options.onReady) + tab.on('ready', options.onReady); + + if (options.onLoad) + tab.on('load', options.onLoad); + + if (options.onPageShow) + tab.on('pageshow', options.onPageShow); + + if (options.onActivate) + tab.on('activate', options.onActivate); + + return tab; + } +}); +var gTabs = exports.tabs = Tabs(mainWindow); + +function tabsUnloader(event, window) { + window = window || (event && event.target); + if (!(window && window.BrowserApp)) + return; + window.BrowserApp.deck.removeEventListener(EVENTS.open.dom, onTabOpen, false); + window.BrowserApp.deck.removeEventListener(EVENTS.activate.dom, onTabSelect, false); +} + +// unload handler +unload(function() { + for (let window in windowIterator()) { + tabsUnloader(null, window); + } +}); + +function addTab(tab) { + addListItem(gTabs, tab); + return tab; +} + +function removeTab(tab) { + removeListItem(gTabs, tab); + return tab; +} + +// TabOpen +function onTabOpen(event) { + let browser = event.target; + + // Eventually ignore private tabs + if (ignoreWindow(browser.contentWindow)) + return; + + let tab = getTabForBrowser(browser); + if (tab === null) { + let rawTab = getRawTabForBrowser(browser); + + // create a Tab instance for this new tab + tab = addTab(Tab(rawTab)); + } + + tabNS(tab).opened = true; + + tab.on('ready', () => emit(gTabs, 'ready', tab)); + tab.once('close', onTabClose); + + tab.on('pageshow', (_tab, persisted) => + emit(gTabs, 'pageshow', tab, persisted)); + + emit(tab, 'open', tab); + emit(gTabs, 'open', tab); +} + +// TabSelect +function onTabSelect(event) { + let browser = event.target; + + // Eventually ignore private tabs + if (ignoreWindow(browser.contentWindow)) + return; + + // Set value whenever new tab becomes active. + let tab = getTabForBrowser(browser); + emit(tab, 'activate', tab); + emit(gTabs, 'activate', tab); + + for (let t of gTabs) { + if (t === tab) continue; + emit(t, 'deactivate', t); + emit(gTabs, 'deactivate', t); + } +} + +// TabClose +function onTabClose(tab) { + removeTab(tab); + emit(gTabs, EVENTS.close.name, tab); +} -- cgit v1.2.3