diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk')
216 files changed, 0 insertions, 30621 deletions
diff --git a/addon-sdk/source/lib/sdk/addon/bootstrap.js b/addon-sdk/source/lib/sdk/addon/bootstrap.js deleted file mode 100644 index 0397d91e5..000000000 --- a/addon-sdk/source/lib/sdk/addon/bootstrap.js +++ /dev/null @@ -1,182 +0,0 @@ -/* 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 { Cu } = require("chrome"); -const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); -const { Task: { spawn } } = require("resource://gre/modules/Task.jsm"); -const { readURI } = require("sdk/net/url"); -const { mount, unmount } = require("sdk/uri/resource"); -const { setTimeout } = require("sdk/timers"); -const { Loader, Require, Module, main, unload } = require("toolkit/loader"); -const prefs = require("sdk/preferences/service"); - -// load below now, so that it can be used by sdk/addon/runner -// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239 -const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}); - -const REASON = [ "unknown", "startup", "shutdown", "enable", "disable", - "install", "uninstall", "upgrade", "downgrade" ]; - -const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/; -// Takes add-on ID and normalizes it to a domain name so that add-on -// can be mapped to resource://domain/ -const readDomain = id => - // If only `@` character is the first one, than just substract it, - // otherwise fallback to legacy normalization code path. Note: `.` - // is valid character for resource substitutaiton & we intend to - // make add-on URIs intuitive, so it's best to just stick to an - // add-on author typed input. - id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() : - id.toLowerCase(). - replace(/@/g, "-at-"). - replace(/\./g, "-dot-"). - replace(UUID_PATTERN, "$1"); - -const readPaths = id => { - const base = `extensions.modules.${id}.path.`; - const domain = readDomain(id); - return prefs.keys(base).reduce((paths, key) => { - const value = prefs.get(key); - const name = key.replace(base, ""); - const path = name.split(".").join("/"); - const prefix = path.length ? `${path}/` : path; - const uri = value.endsWith("/") ? value : `${value}/`; - const root = `extensions.modules.${domain}.commonjs.path.${name}`; - - mount(root, uri); - - paths[prefix] = `resource://${root}/`; - return paths; - }, {}); -}; - -const Bootstrap = function(mountURI) { - this.mountURI = mountURI; - this.install = this.install.bind(this); - this.uninstall = this.uninstall.bind(this); - this.startup = this.startup.bind(this); - this.shutdown = this.shutdown.bind(this); -}; -Bootstrap.prototype = { - constructor: Bootstrap, - mount(domain, rootURI) { - mount(domain, rootURI); - this.domain = domain; - }, - unmount() { - if (this.domain) { - unmount(this.domain); - this.domain = null; - } - }, - install(addon, reason) { - return new Promise(resolve => resolve()); - }, - uninstall(addon, reason) { - return new Promise(resolve => { - const {id} = addon; - - prefs.reset(`extensions.${id}.sdk.domain`); - prefs.reset(`extensions.${id}.sdk.version`); - prefs.reset(`extensions.${id}.sdk.rootURI`); - prefs.reset(`extensions.${id}.sdk.baseURI`); - prefs.reset(`extensions.${id}.sdk.load.reason`); - - resolve(); - }); - }, - startup(addon, reasonCode) { - const { id, version, resourceURI: { spec: addonURI } } = addon; - const rootURI = this.mountURI || addonURI; - const reason = REASON[reasonCode]; - const self = this; - - return spawn(function*() { - const metadata = JSON.parse(yield readURI(`${rootURI}package.json`)); - const domain = readDomain(id); - const baseURI = `resource://${domain}/`; - - this.mount(domain, rootURI); - - prefs.set(`extensions.${id}.sdk.domain`, domain); - prefs.set(`extensions.${id}.sdk.version`, version); - prefs.set(`extensions.${id}.sdk.rootURI`, rootURI); - prefs.set(`extensions.${id}.sdk.baseURI`, baseURI); - prefs.set(`extensions.${id}.sdk.load.reason`, reason); - - const command = prefs.get(`extensions.${id}.sdk.load.command`); - - const loader = Loader({ - id, - isNative: true, - checkCompatibility: true, - prefixURI: baseURI, - rootURI: baseURI, - name: metadata.name, - paths: Object.assign({ - "": "resource://gre/modules/commonjs/", - "devtools/": "resource://devtools/", - "./": baseURI - }, readPaths(id)), - manifest: metadata, - metadata: metadata, - modules: { - "@test/options": {}, - }, - noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false) - }); - self.loader = loader; - - const module = Module("package.json", `${baseURI}package.json`); - const require = Require(loader, module); - const main = command === "test" ? "sdk/test/runner" : null; - const prefsURI = `${baseURI}defaults/preferences/prefs.js`; - - // Init the 'sdk/webextension' module from the bootstrap addon parameter. - require("sdk/webextension").initFromBootstrapAddonParam(addon); - - const { startup } = require("sdk/addon/runner"); - startup(reason, {loader, main, prefsURI}); - }.bind(this)).catch(error => { - console.error(`Failed to start ${id} addon`, error); - throw error; - }); - }, - shutdown(addon, code) { - this.unmount(); - return this.unload(REASON[code]); - }, - unload(reason) { - return new Promise(resolve => { - const { loader } = this; - if (loader) { - this.loader = null; - unload(loader, reason); - - setTimeout(() => { - for (let uri of Object.keys(loader.sandboxes)) { - let sandbox = loader.sandboxes[uri]; - if (Cu.getClassName(sandbox, true) == "Sandbox") - Cu.nukeSandbox(sandbox); - delete loader.sandboxes[uri]; - delete loader.modules[uri]; - } - - try { - Cu.nukeSandbox(loader.sharedGlobalSandbox); - } catch (e) { - Cu.reportError(e); - } - - resolve(); - }, 1000); - } - else { - resolve(); - } - }); - } -}; -exports.Bootstrap = Bootstrap; diff --git a/addon-sdk/source/lib/sdk/addon/events.js b/addon-sdk/source/lib/sdk/addon/events.js deleted file mode 100644 index 45bada6e1..000000000 --- a/addon-sdk/source/lib/sdk/addon/events.js +++ /dev/null @@ -1,56 +0,0 @@ -/* 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': 'experimental' -}; - -var { request: hostReq, response: hostRes } = require('./host'); -var { defer: async } = require('../lang/functional'); -var { defer } = require('../core/promise'); -var { emit: emitSync, on, off } = require('../event/core'); -var { uuid } = require('../util/uuid'); -var emit = async(emitSync); - -// Map of IDs to deferreds -var requests = new Map(); - -// May not be necessary to wrap this in `async` -// once promises are async via bug 881047 -var receive = async(function ({data, id, error}) { - let request = requests.get(id); - if (request) { - if (error) request.reject(error); - else request.resolve(clone(data)); - requests.delete(id); - } -}); -on(hostRes, 'data', receive); - -/* - * Send is a helper to be used in client APIs to send - * a request to host - */ -function send (eventName, data) { - let id = uuid(); - let deferred = defer(); - requests.set(id, deferred); - emit(hostReq, 'data', { - id: id, - data: clone(data), - event: eventName - }); - return deferred.promise; -} -exports.send = send; - -/* - * Implement internal structured cloning algorithm in the future? - * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#internal-structured-cloning-algorithm - */ -function clone (obj) { - return JSON.parse(JSON.stringify(obj || {})); -} diff --git a/addon-sdk/source/lib/sdk/addon/host.js b/addon-sdk/source/lib/sdk/addon/host.js deleted file mode 100644 index 91aa0e869..000000000 --- a/addon-sdk/source/lib/sdk/addon/host.js +++ /dev/null @@ -1,12 +0,0 @@ -/* 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": "experimental" -}; - -exports.request = {}; -exports.response = {}; diff --git a/addon-sdk/source/lib/sdk/addon/installer.js b/addon-sdk/source/lib/sdk/addon/installer.js deleted file mode 100644 index bb8cf8d16..000000000 --- a/addon-sdk/source/lib/sdk/addon/installer.js +++ /dev/null @@ -1,121 +0,0 @@ -/* 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/. */ - -module.metadata = { - "stability": "experimental" -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm"); -const { defer } = require("../core/promise"); -const { setTimeout } = require("../timers"); - -/** - * `install` method error codes: - * - * https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors - */ -exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE; -exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH; -exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE; -exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS; - -/** - * Immediatly install an addon. - * - * @param {String} xpiPath - * file path to an xpi file to install - * @return {Promise} - * A promise resolved when the addon is finally installed. - * Resolved with addon id as value or rejected with an error code. - */ -exports.install = function install(xpiPath) { - let { promise, resolve, reject } = defer(); - - // Create nsIFile for the xpi file - let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); - try { - file.initWithPath(xpiPath); - } - catch(e) { - reject(exports.ERROR_FILE_ACCESS); - return promise; - } - - // Listen for installation end - let listener = { - onInstallEnded: function(aInstall, aAddon) { - aInstall.removeListener(listener); - // Bug 749745: on FF14+, onInstallEnded is called just before `startup()` - // is called, but we expect to resolve the promise only after it. - // As startup is called synchronously just after onInstallEnded, - // a simple setTimeout(0) is enough - setTimeout(resolve, 0, aAddon.id); - }, - onInstallFailed: function (aInstall) { - aInstall.removeListener(listener); - reject(aInstall.error); - }, - onDownloadFailed: function(aInstall) { - this.onInstallFailed(aInstall); - } - }; - - // Order AddonManager to install the addon - AddonManager.getInstallForFile(file, function(install) { - if (install.error == 0) { - install.addListener(listener); - install.install(); - } else { - reject(install.error); - } - }); - - return promise; -}; - -exports.uninstall = function uninstall(addonId) { - let { promise, resolve, reject } = defer(); - - // Listen for uninstallation end - let listener = { - onUninstalled: function onUninstalled(aAddon) { - if (aAddon.id != addonId) - return; - AddonManager.removeAddonListener(listener); - resolve(); - } - }; - AddonManager.addAddonListener(listener); - - // Order Addonmanager to uninstall the addon - getAddon(addonId).then(addon => addon.uninstall(), reject); - - return promise; -}; - -exports.disable = function disable(addonId) { - return getAddon(addonId).then(addon => { - addon.userDisabled = true; - return addonId; - }); -}; - -exports.enable = function enabled(addonId) { - return getAddon(addonId).then(addon => { - addon.userDisabled = false; - return addonId; - }); -}; - -exports.isActive = function isActive(addonId) { - return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled); -}; - -const getAddon = function getAddon (id) { - let { promise, resolve, reject } = defer(); - AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject()); - return promise; -} -exports.getAddon = getAddon; diff --git a/addon-sdk/source/lib/sdk/addon/manager.js b/addon-sdk/source/lib/sdk/addon/manager.js deleted file mode 100644 index 7ac0a7d6e..000000000 --- a/addon-sdk/source/lib/sdk/addon/manager.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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": "experimental" -}; - -const { AddonManager } = require("resource://gre/modules/AddonManager.jsm"); -const { defer } = require("../core/promise"); - -function getAddonByID(id) { - let { promise, resolve } = defer(); - AddonManager.getAddonByID(id, resolve); - return promise; -} -exports.getAddonByID = getAddonByID; diff --git a/addon-sdk/source/lib/sdk/addon/runner.js b/addon-sdk/source/lib/sdk/addon/runner.js deleted file mode 100644 index 3977a04e4..000000000 --- a/addon-sdk/source/lib/sdk/addon/runner.js +++ /dev/null @@ -1,180 +0,0 @@ -/* 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/. */ - -module.metadata = { - "stability": "experimental" -}; - -const { Cc, Ci, Cu } = require('chrome'); -const { rootURI, metadata, isNative } = require('@loader/options'); -const { id, loadReason } = require('../self'); -const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader'); -const { once } = require('../system/events'); -const { exit, env, staticArgs } = require('../system'); -const { when: unload } = require('../system/unload'); -const globals = require('../system/globals'); -const xulApp = require('../system/xul-app'); -const { get } = require('../preferences/service'); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); -const { preferences } = metadata; - -const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () { - return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}). - BrowserToolboxProcess; -}); - -// Initializes default preferences -function setDefaultPrefs(prefsURI) { - const prefs = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch2); - const branch = prefs.getDefaultBranch(''); - const sandbox = Sandbox({ - name: prefsURI, - prototype: { - pref: function(key, val) { - switch (typeof val) { - case 'boolean': - branch.setBoolPref(key, val); - break; - case 'number': - if (val % 1 == 0) // number must be a integer, otherwise ignore it - branch.setIntPref(key, val); - break; - case 'string': - branch.setCharPref(key, val); - break; - } - } - } - }); - // load preferences. - evaluate(sandbox, prefsURI); -} - -function definePseudo(loader, id, exports) { - let uri = resolveURI(id, loader.mapping); - loader.modules[uri] = { exports: exports }; -} - -function startup(reason, options) { - return Startup.onceInitialized.then(() => { - // Inject globals ASAP in order to have console API working ASAP - Object.defineProperties(options.loader.globals, descriptor(globals)); - - // NOTE: Module is intentionally required only now because it relies - // on existence of hidden window, which does not exists until startup. - let { ready } = require('../addon/window'); - // Load localization manifest and .properties files. - // Run the addon even in case of error (best effort approach) - require('../l10n/loader'). - load(rootURI). - then(null, function failure(error) { - if (!isNative) - console.info("Error while loading localization: " + error.message); - }). - then(function onLocalizationReady(data) { - // Exports data to a pseudo module so that api-utils/l10n/core - // can get access to it - definePseudo(options.loader, '@l10n/data', data ? data : null); - return ready; - }).then(function() { - run(options); - }).then(null, console.exception); - return void 0; // otherwise we raise a warning, see bug 910304 - }); -} - -function run(options) { - try { - // Try initializing HTML localization before running main module. Just print - // an exception in case of error, instead of preventing addon to be run. - try { - // Do not enable HTML localization while running test as it is hard to - // disable. Because unit tests are evaluated in a another Loader who - // doesn't have access to this current loader. - if (options.main !== 'sdk/test/runner') { - require('../l10n/html').enable(); - } - } - catch(error) { - console.exception(error); - } - - // native-options does stuff directly with preferences key from package.json - if (preferences && preferences.length > 0) { - try { - require('../preferences/native-options'). - enable({ preferences: preferences, id: id }). - catch(console.exception); - } - catch (error) { - console.exception(error); - } - } - else { - // keeping support for addons packaged with older SDK versions, - // when cfx didn't include the 'preferences' key in @loader/options - - // Initialize inline options localization, without preventing addon to be - // run in case of error - try { - require('../l10n/prefs').enable(); - } - catch(error) { - console.exception(error); - } - - // TODO: When bug 564675 is implemented this will no longer be needed - // Always set the default prefs, because they disappear on restart - if (options.prefsURI) { - // Only set if `prefsURI` specified - try { - setDefaultPrefs(options.prefsURI); - } - catch (err) { - // cfx bootstrap always passes prefsURI, even in addons without prefs - } - } - } - - // this is where the addon's main.js finally run. - let program = main(options.loader, options.main); - - if (typeof(program.onUnload) === 'function') - unload(program.onUnload); - - if (typeof(program.main) === 'function') { - program.main({ - loadReason: loadReason, - staticArgs: staticArgs - }, { - print: function print(_) { dump(_ + '\n') }, - quit: exit - }); - } - - if (get("extensions." + id + ".sdk.debug.show", false)) { - BrowserToolboxProcess.init({ addonID: id }); - } - } catch (error) { - console.exception(error); - throw error; - } -} -exports.startup = startup; - -// If add-on is lunched via `cfx run` we need to use `system.exit` to let -// cfx know we're done (`cfx test` will take care of exit so we don't do -// anything here). -if (env.CFX_COMMAND === 'run') { - unload(function(reason) { - if (reason === 'shutdown') - exit(0); - }); -} diff --git a/addon-sdk/source/lib/sdk/addon/window.js b/addon-sdk/source/lib/sdk/addon/window.js deleted file mode 100644 index 93ed1d8dc..000000000 --- a/addon-sdk/source/lib/sdk/addon/window.js +++ /dev/null @@ -1,66 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci, Cc } = require("chrome"); -const { make: makeWindow, getHiddenWindow } = require("../window/utils"); -const { create: makeFrame, getDocShell } = require("../frame/utils"); -const { defer } = require("../core/promise"); -const { when: unload } = require("../system/unload"); -const cfxArgs = require("../test/options"); - -var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"]. - createInstance(Ci.nsIPrincipal); - -var hiddenWindow = getHiddenWindow(); - -if (cfxArgs.parseable) { - console.info("hiddenWindow document.documentURI:" + - hiddenWindow.document.documentURI); - console.info("hiddenWindow document.readyState:" + - hiddenWindow.document.readyState); -} - -// Once Bug 565388 is fixed and shipped we'll be able to make invisible, -// permanent docShells. Meanwhile we create hidden top level window and -// use it's docShell. -var frame = makeFrame(hiddenWindow.document, { - nodeName: "iframe", - namespaceURI: "http://www.w3.org/1999/xhtml", - allowJavascript: true, - allowPlugins: true -}) -var docShell = getDocShell(frame); -var eventTarget = docShell.chromeEventHandler; - -// We need to grant docShell system principals in order to load XUL document -// from data URI into it. -docShell.createAboutBlankContentViewer(addonPrincipal); - -// Get a reference to the DOM window of the given docShell and load -// such document into that would allow us to create XUL iframes, that -// are necessary for hidden frames etc.. -var window = docShell.contentViewer.DOMDocument.defaultView; -window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>"; - -// Create a promise that is delivered once add-on window is interactive, -// used by add-on runner to defer add-on loading until window is ready. -var { promise, resolve } = defer(); -eventTarget.addEventListener("DOMContentLoaded", function handler(event) { - eventTarget.removeEventListener("DOMContentLoaded", handler, false); - resolve(); -}, false); - -exports.ready = promise; -exports.window = window; - -// Still close window on unload to claim memory back early. -unload(function() { - window.close() - frame.parentNode.removeChild(frame); -}); diff --git a/addon-sdk/source/lib/sdk/base64.js b/addon-sdk/source/lib/sdk/base64.js deleted file mode 100644 index a07b302e0..000000000 --- a/addon-sdk/source/lib/sdk/base64.js +++ /dev/null @@ -1,47 +0,0 @@ -/* 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 { Cu } = require("chrome"); - -// Passing an empty object as second argument to avoid scope's pollution -// (devtools loader injects these symbols as global and prevent using -// const here) -var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {}); - -function isUTF8(charset) { - let type = typeof charset; - - if (type === "undefined") - return false; - - if (type === "string" && charset.toLowerCase() === "utf-8") - return true; - - throw new Error("The charset argument can be only 'utf-8'"); -} - -function toOctetChar(c) { - return String.fromCharCode(c.charCodeAt(0) & 0xFF); -} - -exports.decode = function (data, charset) { - if (isUTF8(charset)) - return decodeURIComponent(escape(atob(data))) - - return atob(data); -} - -exports.encode = function (data, charset) { - if (isUTF8(charset)) - return btoa(unescape(encodeURIComponent(data))) - - data = data.replace(/[^\x00-\xFF]/g, toOctetChar); - return btoa(data); -} diff --git a/addon-sdk/source/lib/sdk/browser/events.js b/addon-sdk/source/lib/sdk/browser/events.js deleted file mode 100644 index f91119031..000000000 --- a/addon-sdk/source/lib/sdk/browser/events.js +++ /dev/null @@ -1,20 +0,0 @@ -/* 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 { events } = require("../window/events"); -const { filter } = require("../event/utils"); -const { isBrowser } = require("../window/utils"); - -// TODO: `isBrowser` detects weather window is a browser by checking -// `windowtype` attribute, which means that all 'open' events will be -// filtered out since document is not loaded yet. Maybe we can find a better -// implementation for `isBrowser`. Either way it's not really needed yet -// neither window tracker provides this event. - -exports.events = filter(events, ({target}) => isBrowser(target)); diff --git a/addon-sdk/source/lib/sdk/clipboard.js b/addon-sdk/source/lib/sdk/clipboard.js deleted file mode 100644 index 048d5f2f1..000000000 --- a/addon-sdk/source/lib/sdk/clipboard.js +++ /dev/null @@ -1,337 +0,0 @@ -/* 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": "stable", - "engines": { - // TODO Fennec Support 789757 - "Firefox": "*", - "SeaMonkey": "*", - "Thunderbird": "*" - } -}; - -const { Cc, Ci } = require("chrome"); -const { DataURL } = require("./url"); -const apiUtils = require("./deprecated/api-utils"); -/* -While these data flavors resemble Internet media types, they do -no directly map to them. -*/ -const kAllowableFlavors = [ - "text/unicode", - "text/html", - "image/png" - /* CURRENTLY UNSUPPORTED FLAVORS - "text/plain", - "image/jpg", - "image/jpeg", - "image/gif", - "text/x-moz-text-internal", - "AOLMAIL", - "application/x-moz-file", - "text/x-moz-url", - "text/x-moz-url-data", - "text/x-moz-url-desc", - "text/x-moz-url-priv", - "application/x-moz-nativeimage", - "application/x-moz-nativehtml", - "application/x-moz-file-promise-url", - "application/x-moz-file-promise-dest-filename", - "application/x-moz-file-promise", - "application/x-moz-file-promise-dir" - */ -]; - -/* -Aliases for common flavors. Not all flavors will -get an alias. New aliases must be approved by a -Jetpack API druid. -*/ -const kFlavorMap = [ - { short: "text", long: "text/unicode" }, - { short: "html", long: "text/html" }, - { short: "image", long: "image/png" } -]; - -var clipboardService = Cc["@mozilla.org/widget/clipboard;1"]. - getService(Ci.nsIClipboard); - -var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]. - getService(Ci.nsIClipboardHelper); - -var imageTools = Cc["@mozilla.org/image/tools;1"]. - getService(Ci.imgITools); - -exports.set = function(aData, aDataType) { - - let options = { - data: aData, - datatype: aDataType || "text" - }; - - // If `aDataType` is not given or if it's "image", the data is parsed as - // data URL to detect a better datatype - if (aData && (!aDataType || aDataType === "image")) { - try { - let dataURL = new DataURL(aData); - - options.datatype = dataURL.mimeType; - options.data = dataURL.data; - } - catch (e) { - // Ignore invalid URIs - if (e.name !== "URIError") { - throw e; - } - } - } - - options = apiUtils.validateOptions(options, { - data: { - is: ["string"] - }, - datatype: { - is: ["string"] - } - }); - - let flavor = fromJetpackFlavor(options.datatype); - - if (!flavor) - throw new Error("Invalid flavor for " + options.datatype); - - // Additional checks for using the simple case - if (flavor == "text/unicode") { - clipboardHelper.copyString(options.data); - return true; - } - - // Below are the more complex cases where we actually have to work with a - // nsITransferable object - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - if (!xferable) - throw new Error("Couldn't set the clipboard due to an internal error " + - "(couldn't create a Transferable object)."); - // Bug 769440: Starting with FF16, transferable have to be inited - if ("init" in xferable) - xferable.init(null); - - switch (flavor) { - case "text/html": - // add text/html flavor - let str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - - str.data = options.data; - xferable.addDataFlavor(flavor); - xferable.setTransferData(flavor, str, str.data.length * 2); - - // add a text/unicode flavor (html converted to plain text) - str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - let converter = Cc["@mozilla.org/feed-textconstruct;1"]. - createInstance(Ci.nsIFeedTextConstruct); - - converter.type = "html"; - converter.text = options.data; - str.data = converter.plainText(); - xferable.addDataFlavor("text/unicode"); - xferable.setTransferData("text/unicode", str, str.data.length * 2); - break; - - // Set images to the clipboard is not straightforward, to have an idea how - // it works on platform side, see: - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530 - case "image/png": - let image = options.data; - - let container = {}; - - try { - let input = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - - input.setData(image, image.length); - - imageTools.decodeImageData(input, flavor, container); - } - catch (e) { - throw new Error("Unable to decode data given in a valid image."); - } - - // Store directly the input stream makes the cliboard's data available - // for Firefox but not to the others application or to the OS. Therefore, - // a `nsISupportsInterfacePointer` object that reference an `imgIContainer` - // with the image is needed. - var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"]. - createInstance(Ci.nsISupportsInterfacePointer); - - imgPtr.data = container.value; - - xferable.addDataFlavor(flavor); - xferable.setTransferData(flavor, imgPtr, -1); - - break; - default: - throw new Error("Unable to handle the flavor " + flavor + "."); - } - - // TODO: Not sure if this will ever actually throw. -zpao - try { - clipboardService.setData( - xferable, - null, - clipboardService.kGlobalClipboard - ); - } catch (e) { - throw new Error("Couldn't set clipboard data due to an internal error: " + e); - } - return true; -}; - - -exports.get = function(aDataType) { - let options = { - datatype: aDataType - }; - - // Figure out the best data type for the clipboard's data, if omitted - if (!aDataType) { - if (~currentFlavors().indexOf("image")) - options.datatype = "image"; - else - options.datatype = "text"; - } - - options = apiUtils.validateOptions(options, { - datatype: { - is: ["string"] - } - }); - - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - if (!xferable) - throw new Error("Couldn't set the clipboard due to an internal error " + - "(couldn't create a Transferable object)."); - // Bug 769440: Starting with FF16, transferable have to be inited - if ("init" in xferable) - xferable.init(null); - - var flavor = fromJetpackFlavor(options.datatype); - - // Ensure that the user hasn't requested a flavor that we don't support. - if (!flavor) - throw new Error("Getting the clipboard with the flavor '" + flavor + - "' is not supported."); - - // TODO: Check for matching flavor first? Probably not worth it. - - xferable.addDataFlavor(flavor); - // Get the data into our transferable. - clipboardService.getData( - xferable, - clipboardService.kGlobalClipboard - ); - - var data = {}; - var dataLen = {}; - try { - xferable.getTransferData(flavor, data, dataLen); - } catch (e) { - // Clipboard doesn't contain data in flavor, return null. - return null; - } - - // There's no data available, return. - if (data.value === null) - return null; - - // TODO: Add flavors here as we support more in kAllowableFlavors. - switch (flavor) { - case "text/unicode": - case "text/html": - data = data.value.QueryInterface(Ci.nsISupportsString).data; - break; - case "image/png": - let dataURL = new DataURL(); - - dataURL.mimeType = flavor; - dataURL.base64 = true; - - let image = data.value; - - // Due to the differences in how images could be stored in the clipboard - // the checks below are needed. The clipboard could already provide the - // image as byte streams, but also as pointer, or as image container. - // If it's not possible obtain a byte stream, the function returns `null`. - if (image instanceof Ci.nsISupportsInterfacePointer) - image = image.data; - - if (image instanceof Ci.imgIContainer) - image = imageTools.encodeImage(image, flavor); - - if (image instanceof Ci.nsIInputStream) { - let binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIBinaryInputStream); - - binaryStream.setInputStream(image); - - dataURL.data = binaryStream.readBytes(binaryStream.available()); - - data = dataURL.toString(); - } - else - data = null; - - break; - default: - data = null; - } - - return data; -}; - -function currentFlavors() { - // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each. - // This doesn't seem like the most efficient way, but we can't get - // confirmation for specific flavors any other way. This is supposed to be - // an inexpensive call, so performance shouldn't be impacted (much). - var currentFlavors = []; - for (var flavor of kAllowableFlavors) { - var matches = clipboardService.hasDataMatchingFlavors( - [flavor], - 1, - clipboardService.kGlobalClipboard - ); - if (matches) - currentFlavors.push(toJetpackFlavor(flavor)); - } - return currentFlavors; -}; - -Object.defineProperty(exports, "currentFlavors", { get : currentFlavors }); - -// SUPPORT FUNCTIONS //////////////////////////////////////////////////////// - -function toJetpackFlavor(aFlavor) { - for (let flavorMap of kFlavorMap) - if (flavorMap.long == aFlavor) - return flavorMap.short; - // Return null in the case where we don't match - return null; -} - -function fromJetpackFlavor(aJetpackFlavor) { - // TODO: Handle proper flavors better - for (let flavorMap of kFlavorMap) - if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor) - return flavorMap.long; - // Return null in the case where we don't match. - return null; -} diff --git a/addon-sdk/source/lib/sdk/console/plain-text.js b/addon-sdk/source/lib/sdk/console/plain-text.js deleted file mode 100644 index 0e44cf106..000000000 --- a/addon-sdk/source/lib/sdk/console/plain-text.js +++ /dev/null @@ -1,78 +0,0 @@ -/* 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 { Cc, Ci, Cu, Cr } = require("chrome"); -const self = require("../self"); -const prefs = require("../preferences/service"); -const { merge } = require("../util/object"); -const { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {}); - -const DEFAULT_LOG_LEVEL = "error"; -const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel"; -const SDK_LOG_LEVEL_PREF = "extensions.sdk.console.logLevel"; - -var logLevel = DEFAULT_LOG_LEVEL; -function setLogLevel() { - logLevel = prefs.get(ADDON_LOG_LEVEL_PREF, - prefs.get(SDK_LOG_LEVEL_PREF, - DEFAULT_LOG_LEVEL)); -} -setLogLevel(); - -var logLevelObserver = { - QueryInterface: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - }, - observe: function(subject, topic, data) { - setLogLevel(); - } -}; -var branch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - getBranch(null); -branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true); -branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true); - -function PlainTextConsole(print, innerID) { - - let consoleOptions = { - prefix: self.name, - maxLogLevel: logLevel, - dump: print, - innerID: innerID, - consoleID: "addon/" + self.id - }; - let console = new ConsoleAPI(consoleOptions); - - // As we freeze the console object, we can't modify this property afterward - Object.defineProperty(console, "maxLogLevel", { - get: function() { - return logLevel; - } - }); - - // We defined the `__exposedProps__` in our console chrome object. - // - // Meanwhile we're investigating with the platform team if `__exposedProps__` - // are needed, or are just a left-over. - - console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) { - exposed[prop] = "r"; - return exposed; - }, {}); - - Object.freeze(console); - return console; -}; -exports.PlainTextConsole = PlainTextConsole; diff --git a/addon-sdk/source/lib/sdk/console/traceback.js b/addon-sdk/source/lib/sdk/console/traceback.js deleted file mode 100644 index be0fb7b94..000000000 --- a/addon-sdk/source/lib/sdk/console/traceback.js +++ /dev/null @@ -1,86 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci, components } = require("chrome"); -const { parseStack, sourceURI } = require("toolkit/loader"); -const { readURISync } = require("../net/url"); - -function safeGetFileLine(path, line) { - try { - var scheme = require("../url").URL(path).scheme; - // TODO: There should be an easier, more accurate way to figure out - // what's the case here. - if (!(scheme == "http" || scheme == "https")) - return readURISync(path).split("\n")[line - 1]; - } catch (e) {} - return null; -} - -function nsIStackFramesToJSON(frame) { - var stack = []; - - while (frame) { - if (frame.filename) { - stack.unshift({ - fileName: sourceURI(frame.filename), - lineNumber: frame.lineNumber, - name: frame.name - }); - } - frame = frame.caller; - } - - return stack; -}; - -var fromException = exports.fromException = function fromException(e) { - if (e instanceof Ci.nsIException) - return nsIStackFramesToJSON(e.location); - if (e.stack && e.stack.length) - return parseStack(e.stack); - if (e.fileName && typeof(e.lineNumber == "number")) - return [{fileName: sourceURI(e.fileName), - lineNumber: e.lineNumber, - name: null}]; - return []; -}; - -var get = exports.get = function get() { - return nsIStackFramesToJSON(components.stack.caller); -}; - -var format = exports.format = function format(tbOrException) { - if (tbOrException === undefined) { - tbOrException = get(); - tbOrException.pop(); - } - - var tb; - if (typeof(tbOrException) == "object" && - tbOrException.constructor.name == "Array") - tb = tbOrException; - else - tb = fromException(tbOrException); - - var lines = ["Traceback (most recent call last):"]; - - tb.forEach( - function(frame) { - if (!(frame.fileName || frame.lineNumber || frame.name)) - return; - - lines.push(' File "' + frame.fileName + '", line ' + - frame.lineNumber + ', in ' + frame.name); - var sourceLine = safeGetFileLine(frame.fileName, frame.lineNumber); - if (sourceLine) - lines.push(' ' + sourceLine.trim()); - }); - - return lines.join("\n"); -}; diff --git a/addon-sdk/source/lib/sdk/content/content-worker.js b/addon-sdk/source/lib/sdk/content/content-worker.js deleted file mode 100644 index 0a8225733..000000000 --- a/addon-sdk/source/lib/sdk/content/content-worker.js +++ /dev/null @@ -1,305 +0,0 @@ -/* 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/. */ - -Object.freeze({ - // TODO: Bug 727854 Use same implementation than common JS modules, - // i.e. EventEmitter module - - /** - * Create an EventEmitter instance. - */ - createEventEmitter: function createEventEmitter(emit) { - let listeners = Object.create(null); - let eventEmitter = Object.freeze({ - emit: emit, - on: function on(name, callback) { - if (typeof callback !== "function") - return this; - if (!(name in listeners)) - listeners[name] = []; - listeners[name].push(callback); - return this; - }, - once: function once(name, callback) { - eventEmitter.on(name, function onceCallback() { - eventEmitter.removeListener(name, onceCallback); - callback.apply(callback, arguments); - }); - }, - removeListener: function removeListener(name, callback) { - if (!(name in listeners)) - return; - let index = listeners[name].indexOf(callback); - if (index == -1) - return; - listeners[name].splice(index, 1); - } - }); - function onEvent(name) { - if (!(name in listeners)) - return []; - let args = Array.slice(arguments, 1); - let results = []; - for (let callback of listeners[name]) { - results.push(callback.apply(null, args)); - } - return results; - } - return { - eventEmitter: eventEmitter, - emit: onEvent - }; - }, - - /** - * Create an EventEmitter instance to communicate with chrome module - * by passing only strings between compartments. - * This function expects `emitToChrome` function, that allows to send - * events to the chrome module. It returns the EventEmitter as `pipe` - * attribute, and, `onChromeEvent` a function that allows chrome module - * to send event into the EventEmitter. - * - * pipe.emit --> emitToChrome - * onChromeEvent --> callback registered through pipe.on - */ - createPipe: function createPipe(emitToChrome) { - let ContentWorker = this; - function onEvent(type, ...args) { - // JSON.stringify is buggy with cross-sandbox values, - // it may return "{}" on functions. Use a replacer to match them correctly. - let replacer = (k, v) => - typeof(v) === "function" - ? (type === "console" ? Function.toString.call(v) : void(0)) - : v; - - let str = JSON.stringify([type, ...args], replacer); - emitToChrome(str); - } - - let { eventEmitter, emit } = - ContentWorker.createEventEmitter(onEvent); - - return { - pipe: eventEmitter, - onChromeEvent: function onChromeEvent(array) { - // We either receive a stringified array, or a real array. - // We still allow to pass an array of objects, in WorkerSandbox.emitSync - // in order to allow sending DOM node reference between content script - // and modules (only used for context-menu API) - let args = typeof array == "string" ? JSON.parse(array) : array; - return emit.apply(null, args); - } - }; - }, - - injectConsole: function injectConsole(exports, pipe) { - exports.console = Object.freeze({ - log: pipe.emit.bind(null, "console", "log"), - info: pipe.emit.bind(null, "console", "info"), - warn: pipe.emit.bind(null, "console", "warn"), - error: pipe.emit.bind(null, "console", "error"), - debug: pipe.emit.bind(null, "console", "debug"), - exception: pipe.emit.bind(null, "console", "exception"), - trace: pipe.emit.bind(null, "console", "trace"), - time: pipe.emit.bind(null, "console", "time"), - timeEnd: pipe.emit.bind(null, "console", "timeEnd") - }); - }, - - injectTimers: function injectTimers(exports, chromeAPI, pipe, console) { - // wrapped functions from `'timer'` module. - // Wrapper adds `try catch` blocks to the callbacks in order to - // emit `error` event if exception is thrown in - // the Worker global scope. - // @see http://www.w3.org/TR/workers/#workerutils - - // List of all living timeouts/intervals - let _timers = Object.create(null); - - // Keep a reference to original timeout functions - let { - setTimeout: chromeSetTimeout, - setInterval: chromeSetInterval, - clearTimeout: chromeClearTimeout, - clearInterval: chromeClearInterval - } = chromeAPI.timers; - - function registerTimer(timer) { - let registerMethod = null; - if (timer.kind == "timeout") - registerMethod = chromeSetTimeout; - else if (timer.kind == "interval") - registerMethod = chromeSetInterval; - else - throw new Error("Unknown timer kind: " + timer.kind); - - if (typeof timer.fun == 'string') { - let code = timer.fun; - timer.fun = () => chromeAPI.sandbox.evaluate(exports, code); - } else if (typeof timer.fun != 'function') { - throw new Error('Unsupported callback type' + typeof timer.fun); - } - - let id = registerMethod(onFire, timer.delay); - function onFire() { - try { - if (timer.kind == "timeout") - delete _timers[id]; - timer.fun.apply(null, timer.args); - } catch(e) { - console.exception(e); - let wrapper = { - instanceOfError: instanceOf(e, Error), - value: e, - }; - if (wrapper.instanceOfError) { - wrapper.value = { - message: e.message, - fileName: e.fileName, - lineNumber: e.lineNumber, - stack: e.stack, - name: e.name, - }; - } - pipe.emit('error', wrapper); - } - } - _timers[id] = timer; - return id; - } - - // copied from sdk/lang/type.js since modules are not available here - function instanceOf(value, Type) { - var isConstructorNameSame; - var isConstructorSourceSame; - - // If `instanceof` returned `true` we know result right away. - var isInstanceOf = value instanceof Type; - - // If `instanceof` returned `false` we do ducktype check since `Type` may be - // from a different sandbox. If a constructor of the `value` or a constructor - // of the value's prototype has same name and source we assume that it's an - // instance of the Type. - if (!isInstanceOf && value) { - isConstructorNameSame = value.constructor.name === Type.name; - isConstructorSourceSame = String(value.constructor) == String(Type); - isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) || - instanceOf(Object.getPrototypeOf(value), Type); - } - return isInstanceOf; - } - - function unregisterTimer(id) { - if (!(id in _timers)) - return; - let { kind } = _timers[id]; - delete _timers[id]; - if (kind == "timeout") - chromeClearTimeout(id); - else if (kind == "interval") - chromeClearInterval(id); - else - throw new Error("Unknown timer kind: " + kind); - } - - function disableAllTimers() { - Object.keys(_timers).forEach(unregisterTimer); - } - - exports.setTimeout = function ContentScriptSetTimeout(callback, delay) { - return registerTimer({ - kind: "timeout", - fun: callback, - delay: delay, - args: Array.slice(arguments, 2) - }); - }; - exports.clearTimeout = function ContentScriptClearTimeout(id) { - unregisterTimer(id); - }; - - exports.setInterval = function ContentScriptSetInterval(callback, delay) { - return registerTimer({ - kind: "interval", - fun: callback, - delay: delay, - args: Array.slice(arguments, 2) - }); - }; - exports.clearInterval = function ContentScriptClearInterval(id) { - unregisterTimer(id); - }; - - // On page-hide, save a list of all existing timers before disabling them, - // in order to be able to restore them on page-show. - // These events are fired when the page goes in/out of bfcache. - // https://developer.mozilla.org/En/Working_with_BFCache - let frozenTimers = []; - pipe.on("pageshow", function onPageShow() { - frozenTimers.forEach(registerTimer); - }); - pipe.on("pagehide", function onPageHide() { - frozenTimers = []; - for (let id in _timers) - frozenTimers.push(_timers[id]); - disableAllTimers(); - // Some other pagehide listeners may register some timers that won't be - // frozen as this particular pagehide listener is called first. - // So freeze these timers on next cycle. - chromeSetTimeout(function () { - for (let id in _timers) - frozenTimers.push(_timers[id]); - disableAllTimers(); - }, 0); - }); - - // Unregister all timers when the page is destroyed - // (i.e. when it is removed from bfcache) - pipe.on("detach", function clearTimeouts() { - disableAllTimers(); - _timers = {}; - frozenTimers = []; - }); - }, - - injectMessageAPI: function injectMessageAPI(exports, pipe, console) { - - let ContentWorker = this; - let { eventEmitter: port, emit : portEmit } = - ContentWorker.createEventEmitter(pipe.emit.bind(null, "event")); - pipe.on("event", portEmit); - - let self = { - port: port, - postMessage: pipe.emit.bind(null, "message"), - on: pipe.on.bind(null), - once: pipe.once.bind(null), - removeListener: pipe.removeListener.bind(null), - }; - Object.defineProperty(exports, "self", { - value: self - }); - }, - - injectOptions: function (exports, options) { - Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) }); - }, - - inject: function (exports, chromeAPI, emitToChrome, options) { - let ContentWorker = this; - let { pipe, onChromeEvent } = - ContentWorker.createPipe(emitToChrome); - - ContentWorker.injectConsole(exports, pipe); - ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console); - ContentWorker.injectMessageAPI(exports, pipe, exports.console); - if ( options !== undefined ) { - ContentWorker.injectOptions(exports, options); - } - - Object.freeze( exports.self ); - - return onChromeEvent; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/content.js b/addon-sdk/source/lib/sdk/content/content.js deleted file mode 100644 index 9655223a3..000000000 --- a/addon-sdk/source/lib/sdk/content/content.js +++ /dev/null @@ -1,17 +0,0 @@ -/* 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": "deprecated" -}; - -const { deprecateUsage } = require('../util/deprecate'); - -Object.defineProperty(exports, "Worker", { - get: function() { - deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.'); - return require('./worker').Worker; - } -}); diff --git a/addon-sdk/source/lib/sdk/content/context-menu.js b/addon-sdk/source/lib/sdk/content/context-menu.js deleted file mode 100644 index 2955e2f09..000000000 --- a/addon-sdk/source/lib/sdk/content/context-menu.js +++ /dev/null @@ -1,408 +0,0 @@ -/* 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 self = require("../self"); -const { WorkerChild } = require("./worker-child"); -const { getInnerId } = require("../window/utils"); -const { Ci } = require("chrome"); -const { Services } = require("resource://gre/modules/Services.jsm"); -const system = require('../system/events'); -const { process } = require('../remote/child'); - -// These functions are roughly copied from sdk/selection which doesn't work -// in the content process -function getElementWithSelection(window) { - let element = Services.focus.getFocusedElementForWindow(window, false, {}); - if (!element) - return null; - - try { - // Accessing selectionStart and selectionEnd on e.g. a button - // results in an exception thrown as per the HTML5 spec. See - // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection - - let { value, selectionStart, selectionEnd } = element; - - let hasSelection = typeof value === "string" && - !isNaN(selectionStart) && - !isNaN(selectionEnd) && - selectionStart !== selectionEnd; - - return hasSelection ? element : null; - } - catch (err) { - console.exception(err); - return null; - } -} - -function safeGetRange(selection, rangeNumber) { - try { - let { rangeCount } = selection; - let range = null; - - for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) { - range = selection.getRangeAt(rangeNumber); - - if (range && range.toString()) - break; - - range = null; - } - - return range; - } - catch (e) { - return null; - } -} - -function getSelection(window) { - let selection = window.getSelection(); - let range = safeGetRange(selection); - if (range) - return range.toString(); - - let node = getElementWithSelection(window); - if (!node) - return null; - - return node.value.substring(node.selectionStart, node.selectionEnd); -} - -//These are used by PageContext.isCurrent below. If the popupNode or any of -//its ancestors is one of these, Firefox uses a tailored context menu, and so -//the page context doesn't apply. -const NON_PAGE_CONTEXT_ELTS = [ - Ci.nsIDOMHTMLAnchorElement, - Ci.nsIDOMHTMLAppletElement, - Ci.nsIDOMHTMLAreaElement, - Ci.nsIDOMHTMLButtonElement, - Ci.nsIDOMHTMLCanvasElement, - Ci.nsIDOMHTMLEmbedElement, - Ci.nsIDOMHTMLImageElement, - Ci.nsIDOMHTMLInputElement, - Ci.nsIDOMHTMLMapElement, - Ci.nsIDOMHTMLMediaElement, - Ci.nsIDOMHTMLMenuElement, - Ci.nsIDOMHTMLObjectElement, - Ci.nsIDOMHTMLOptionElement, - Ci.nsIDOMHTMLSelectElement, - Ci.nsIDOMHTMLTextAreaElement, -]; - -// List all editable types of inputs. Or is it better to have a list -// of non-editable inputs? -var editableInputs = { - email: true, - number: true, - password: true, - search: true, - tel: true, - text: true, - textarea: true, - url: true -}; - -var CONTEXTS = {}; - -var Context = Class({ - initialize: function(id) { - this.id = id; - }, - - adjustPopupNode: function adjustPopupNode(popupNode) { - return popupNode; - }, - - // Gets state to pass through to the parent process for the node the user - // clicked on - getState: function(popupNode) { - return false; - } -}); - -// Matches when the context-clicked node doesn't have any of -// NON_PAGE_CONTEXT_ELTS in its ancestors -CONTEXTS.PageContext = Class({ - extends: Context, - - getState: function(popupNode) { - // If there is a selection in the window then this context does not match - if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed) - return false; - - // If the clicked node or any of its ancestors is one of the blocked - // NON_PAGE_CONTEXT_ELTS then this context does not match - while (!(popupNode instanceof Ci.nsIDOMDocument)) { - if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type)) - return false; - - popupNode = popupNode.parentNode; - } - - return true; - } -}); - -// Matches when there is an active selection in the window -CONTEXTS.SelectionContext = Class({ - extends: Context, - - getState: function(popupNode) { - if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed) - return true; - - try { - // The node may be a text box which has selectionStart and selectionEnd - // properties. If not this will throw. - let { selectionStart, selectionEnd } = popupNode; - return !isNaN(selectionStart) && !isNaN(selectionEnd) && - selectionStart !== selectionEnd; - } - catch (e) { - return false; - } - } -}); - -// Matches when the context-clicked node or any of its ancestors matches the -// selector given -CONTEXTS.SelectorContext = Class({ - extends: Context, - - initialize: function initialize(id, selector) { - Context.prototype.initialize.call(this, id); - this.selector = selector; - }, - - adjustPopupNode: function adjustPopupNode(popupNode) { - let selector = this.selector; - - while (!(popupNode instanceof Ci.nsIDOMDocument)) { - if (popupNode.matches(selector)) - return popupNode; - - popupNode = popupNode.parentNode; - } - - return null; - }, - - getState: function(popupNode) { - return !!this.adjustPopupNode(popupNode); - } -}); - -// Matches when the page url matches any of the patterns given -CONTEXTS.URLContext = Class({ - extends: Context, - - getState: function(popupNode) { - return popupNode.ownerDocument.URL; - } -}); - -// Matches when the user-supplied predicate returns true -CONTEXTS.PredicateContext = Class({ - extends: Context, - - getState: function(node) { - let window = node.ownerDocument.defaultView; - let data = {}; - - data.documentType = node.ownerDocument.contentType; - - data.documentURL = node.ownerDocument.location.href; - data.targetName = node.nodeName.toLowerCase(); - data.targetID = node.id || null ; - - if ((data.targetName === 'input' && editableInputs[node.type]) || - data.targetName === 'textarea') { - data.isEditable = !node.readOnly && !node.disabled; - } - else { - data.isEditable = node.isContentEditable; - } - - data.selectionText = getSelection(window, "TEXT"); - - data.srcURL = node.src || null; - data.value = node.value || null; - - while (!data.linkURL && node) { - data.linkURL = node.href || null; - node = node.parentNode; - } - - return data; - }, -}); - -function instantiateContext({ id, type, args }) { - if (!(type in CONTEXTS)) { - console.error("Attempt to use unknown context " + type); - return; - } - return new CONTEXTS[type](id, ...args); -} - -var ContextWorker = Class({ - implements: [ WorkerChild ], - - // Calls the context workers context listeners and returns the first result - // that is either a string or a value that evaluates to true. If all of the - // listeners returned false then returns false. If there are no listeners, - // returns true (show the menu item by default). - getMatchedContext: function getCurrentContexts(popupNode) { - let results = this.sandbox.emitSync("context", popupNode); - if (!results.length) - return true; - return results.reduce((val, result) => val || result); - }, - - // Emits a click event in the worker's port. popupNode is the node that was - // context-clicked, and clickedItemData is the data of the item that was - // clicked. - fireClick: function fireClick(popupNode, clickedItemData) { - this.sandbox.emitSync("click", popupNode, clickedItemData); - } -}); - -// Gets the item's content script worker for a window, creating one if necessary -// Once created it will be automatically destroyed when the window unloads. -// If there is not content scripts for the item then null will be returned. -function getItemWorkerForWindow(item, window) { - if (!item.contentScript && !item.contentScriptFile) - return null; - - let id = getInnerId(window); - let worker = item.workerMap.get(id); - - if (worker) - return worker; - - worker = ContextWorker({ - id: item.id, - window, - manager: item.manager, - contentScript: item.contentScript, - contentScriptFile: item.contentScriptFile, - onDetach: function() { - item.workerMap.delete(id); - } - }); - - item.workerMap.set(id, worker); - - return worker; -} - -// A very simple remote proxy for every item. It's job is to provide data for -// the main process to use to determine visibility state and to call into -// content scripts when clicked. -var RemoteItem = Class({ - initialize: function(options, manager) { - this.id = options.id; - this.contexts = options.contexts.map(instantiateContext); - this.contentScript = options.contentScript; - this.contentScriptFile = options.contentScriptFile; - - this.manager = manager; - - this.workerMap = new Map(); - keepAlive.set(this.id, this); - }, - - destroy: function() { - for (let worker of this.workerMap.values()) { - worker.destroy(); - } - keepAlive.delete(this.id); - }, - - activate: function(popupNode, data) { - let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView); - if (!worker) - return; - - for (let context of this.contexts) - popupNode = context.adjustPopupNode(popupNode); - - worker.fireClick(popupNode, data); - }, - - // Fills addonInfo with state data to send through to the main process - getContextState: function(popupNode, addonInfo) { - if (!(self.id in addonInfo)) { - addonInfo[self.id] = { - processID: process.id, - items: {} - }; - } - - let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView); - let contextStates = {}; - for (let context of this.contexts) - contextStates[context.id] = context.getState(popupNode); - - addonInfo[self.id].items[this.id] = { - // It isn't ideal to create a PageContext for every item but there isn't - // a good shared place to do it. - pageContext: (new CONTEXTS.PageContext()).getState(popupNode), - contextStates, - hasWorker: !!worker, - workerContext: worker ? worker.getMatchedContext(popupNode) : true - } - } -}); -exports.RemoteItem = RemoteItem; - -// Holds remote items for this frame. -var keepAlive = new Map(); - -// Called to create remote proxies for items. If they already exist we destroy -// and recreate. This can happen if the item changes in some way or in odd -// timing cases where the frame script is create around the same time as the -// item is created in the main process -process.port.on('sdk/contextmenu/createitems', (process, items) => { - for (let itemoptions of items) { - let oldItem = keepAlive.get(itemoptions.id); - if (oldItem) { - oldItem.destroy(); - } - - let item = new RemoteItem(itemoptions, this); - } -}); - -process.port.on('sdk/contextmenu/destroyitems', (process, items) => { - for (let id of items) { - let item = keepAlive.get(id); - item.destroy(); - } -}); - -var lastPopupNode = null; - -system.on('content-contextmenu', ({ subject }) => { - let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject; - lastPopupNode = popupNode; - - for (let item of keepAlive.values()) { - item.getContextState(popupNode, addonInfo); - } -}, true); - -process.port.on('sdk/contextmenu/activateitems', (process, items, data) => { - for (let id of items) { - let item = keepAlive.get(id); - if (!item) - continue; - - item.activate(lastPopupNode, data); - } -}); diff --git a/addon-sdk/source/lib/sdk/content/events.js b/addon-sdk/source/lib/sdk/content/events.js deleted file mode 100644 index c085b6179..000000000 --- a/addon-sdk/source/lib/sdk/content/events.js +++ /dev/null @@ -1,57 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci } = require("chrome"); -const { open } = require("../event/dom"); -const { observe } = require("../event/chrome"); -const { filter, merge, map, expand } = require("../event/utils"); -const { windows } = require("../window/utils"); -const { events: windowEvents } = require("sdk/window/events"); - -// Note: Please note that even though pagehide event is included -// it's not observable reliably since it's not always triggered -// when closing tabs. Implementation can be imrpoved once that -// event will be necessary. -var TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"]; - -var insert = observe("document-element-inserted"); -var windowCreate = merge([ - observe("content-document-global-created"), - observe("chrome-document-global-created") -]); -var create = map(windowCreate, function({target, data, type}) { - return { target: target.document, type: type, data: data } -}); - -function streamEventsFrom({document}) { - // Map supported event types to a streams of those events on the given - // `window` for the inserted document and than merge these streams into - // single form stream off all window state change events. - let stateChanges = TYPES.map(function(type) { - return open(document, type, { capture: true }); - }); - - // Since load events on document occur for every loded resource - return filter(merge(stateChanges), function({target}) { - return target instanceof Ci.nsIDOMDocument - }) -} -exports.streamEventsFrom = streamEventsFrom; - -var opened = windows(null, { includePrivate: true }); -var state = merge(opened.map(streamEventsFrom)); - - -var futureReady = filter(windowEvents, ({type}) => - type === "DOMContentLoaded"); -var futureWindows = map(futureReady, ({target}) => target); -var futureState = expand(futureWindows, streamEventsFrom); - -exports.events = merge([insert, create, state, futureState]); diff --git a/addon-sdk/source/lib/sdk/content/l10n-html.js b/addon-sdk/source/lib/sdk/content/l10n-html.js deleted file mode 100644 index f324623dc..000000000 --- a/addon-sdk/source/lib/sdk/content/l10n-html.js +++ /dev/null @@ -1,133 +0,0 @@ -/* 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 { Ci, Cc, Cu } = require("chrome"); -const core = require("../l10n/core"); -const { loadSheet, removeSheet } = require("../stylesheet/utils"); -const { process, frames } = require("../remote/child"); -var observerService = Cc["@mozilla.org/observer-service;1"] - .getService(Ci.nsIObserverService); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - -const assetsURI = require('../self').data.url(); - -const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}"; - -function translateElementAttributes(element) { - // Translateable attributes - const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder']; - const ariaAttrMap = { - 'ariaLabel': 'aria-label', - 'ariaValueText': 'aria-valuetext', - 'ariaMozHint': 'aria-moz-hint' - }; - const attrSeparator = '.'; - - // Try to translate each of the attributes - for (let attribute of attrList) { - const data = core.get(element.dataset.l10nId + attrSeparator + attribute); - if (data) - element.setAttribute(attribute, data); - } - - // Look for the aria attribute translations that match fxOS's aliases - for (let attrAlias in ariaAttrMap) { - const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias); - if (data) - element.setAttribute(ariaAttrMap[attrAlias], data); - } -} - -// Taken from Gaia: -// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470 -function translateElement(element) { - element = element || document; - - // check all translatable children (= w/ a `data-l10n-id' attribute) - var children = element.querySelectorAll('*[data-l10n-id]'); - var elementCount = children.length; - for (var i = 0; i < elementCount; i++) { - var child = children[i]; - - // translate the child - var key = child.dataset.l10nId; - var data = core.get(key); - if (data) - child.textContent = data; - - translateElementAttributes(child); - } -} -exports.translateElement = translateElement; - -function onDocumentReady2Translate(event) { - let document = event.target; - document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate, - false); - - translateElement(document); - - try { - // Finally display document when we finished replacing all text content - if (document.defaultView) - removeSheet(document.defaultView, hideSheetUri, 'user'); - } - catch(e) { - console.exception(e); - } -} - -function onContentWindow(document) { - // Accept only HTML documents - if (!(document instanceof Ci.nsIDOMHTMLDocument)) - return; - - // Bug 769483: data:URI documents instanciated with nsIDOMParser - // have a null `location` attribute at this time - if (!document.location) - return; - - // Accept only document from this addon - if (document.location.href.indexOf(assetsURI) !== 0) - return; - - try { - // First hide content of the document in order to have content blinking - // between untranslated and translated states - loadSheet(document.defaultView, hideSheetUri, 'user'); - } - catch(e) { - console.exception(e); - } - // Wait for DOM tree to be built before applying localization - document.addEventListener("DOMContentLoaded", onDocumentReady2Translate, - false); -} - -// Listen to creation of content documents in order to translate them as soon -// as possible in their loading process -const ON_CONTENT = "document-element-inserted"; -let enabled = false; -function enable() { - if (enabled) - return; - addObserver(onContentWindow, ON_CONTENT, false); - enabled = true; -} -process.port.on("sdk/l10n/html/enable", enable); - -function disable() { - if (!enabled) - return; - removeObserver(onContentWindow, ON_CONTENT); - enabled = false; -} -process.port.on("sdk/l10n/html/disable", disable); diff --git a/addon-sdk/source/lib/sdk/content/loader.js b/addon-sdk/source/lib/sdk/content/loader.js deleted file mode 100644 index e4f0dd2aa..000000000 --- a/addon-sdk/source/lib/sdk/content/loader.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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 { isValidURI, isLocalURL, URL } = require('../url'); -const { contract } = require('../util/contract'); -const { isString, isNil, instanceOf, isJSONable } = require('../lang/type'); -const { validateOptions, - string, array, object, either, required } = require('../deprecated/api-utils'); - -const isValidScriptFile = (value) => - (isString(value) || instanceOf(value, URL)) && isLocalURL(value); - -// map of property validations -const valid = { - contentURL: { - is: either(string, object), - ok: url => isNil(url) || isLocalURL(url) || isValidURI(url), - msg: 'The `contentURL` option must be a valid URL.' - }, - contentScriptFile: { - is: either(string, object, array), - ok: value => isNil(value) || [].concat(value).every(isValidScriptFile), - msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.' - }, - contentScript: { - is: either(string, array), - ok: value => isNil(value) || [].concat(value).every(isString), - msg: 'The `contentScript` option must be a string or an array of strings.' - }, - contentScriptWhen: { - is: required(string), - map: value => value || 'end', - ok: value => ~['start', 'ready', 'end'].indexOf(value), - msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".' - }, - contentScriptOptions: { - ok: value => isNil(value) || isJSONable(value), - msg: 'The contentScriptOptions should be a jsonable value.' - } -}; -exports.validationAttributes = valid; - -/** - * Shortcut function to validate property with validation. - * @param {Object|Number|String} suspect - * value to validate - * @param {Object} validation - * validation rule passed to `api-utils` - */ -function validate(suspect, validation) { - return validateOptions( - { $: suspect }, - { $: validation } - ).$; -} - -function Allow(script) { - return { - get script() { - return script; - }, - set script(value) { - script = !!value; - } - }; -} - -exports.contract = contract(valid); diff --git a/addon-sdk/source/lib/sdk/content/mod.js b/addon-sdk/source/lib/sdk/content/mod.js deleted file mode 100644 index 81fe9ee42..000000000 --- a/addon-sdk/source/lib/sdk/content/mod.js +++ /dev/null @@ -1,68 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci } = require("chrome"); -const { dispatcher } = require("../util/dispatcher"); -const { add, remove, iterator } = require("../lang/weak-set"); - -var getTargetWindow = dispatcher("getTargetWindow"); - -getTargetWindow.define(function (target) { - if (target instanceof Ci.nsIDOMWindow) - return target; - if (target instanceof Ci.nsIDOMDocument) - return target.defaultView || null; - - return null; -}); - -exports.getTargetWindow = getTargetWindow; - -var attachTo = dispatcher("attachTo"); -exports.attachTo = attachTo; - -var detachFrom = dispatcher("detatchFrom"); -exports.detachFrom = detachFrom; - -function attach(modification, target) { - if (!modification) - return; - - let window = getTargetWindow(target); - - attachTo(modification, window); - - // modification are stored per content; `window` reference can still be the - // same even if the content is changed, therefore `document` is used instead. - add(modification, window.document); -} -exports.attach = attach; - -function detach(modification, target) { - if (!modification) - return; - - if (target) { - let window = getTargetWindow(target); - detachFrom(modification, window); - remove(modification, window.document); - } - else { - let documents = iterator(modification); - for (let document of documents) { - let window = document.defaultView; - // The window might have already gone away - if (!window) - continue; - detachFrom(modification, document.defaultView); - remove(modification, document); - } - } -} -exports.detach = detach; diff --git a/addon-sdk/source/lib/sdk/content/page-mod.js b/addon-sdk/source/lib/sdk/content/page-mod.js deleted file mode 100644 index 8ff9b1e7b..000000000 --- a/addon-sdk/source/lib/sdk/content/page-mod.js +++ /dev/null @@ -1,236 +0,0 @@ -/* 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": "stable" -}; - -const { getAttachEventType } = require('../content/utils'); -const { Class } = require('../core/heritage'); -const { Disposable } = require('../core/disposable'); -const { WeakReference } = require('../core/reference'); -const { WorkerChild } = require('./worker-child'); -const { EventTarget } = require('../event/target'); -const { on, emit, once, setListeners } = require('../event/core'); -const { on: domOn, removeListener: domOff } = require('../dom/events'); -const { isRegExp, isUndefined } = require('../lang/type'); -const { merge } = require('../util/object'); -const { isBrowser, getFrames } = require('../window/utils'); -const { getTabs, getURI: getTabURI } = require('../tabs/utils'); -const { ignoreWindow } = require('../private-browsing/utils'); -const { Style } = require("../stylesheet/style"); -const { attach, detach } = require("../content/mod"); -const { has, hasAny } = require("../util/array"); -const { Rules } = require("../util/rules"); -const { List, addListItem, removeListItem } = require('../util/list'); -const { when } = require("../system/unload"); -const { uuid } = require('../util/uuid'); -const { frames, process } = require('../remote/child'); - -const pagemods = new Map(); -const styles = new WeakMap(); -var styleFor = (mod) => styles.get(mod); - -// Helper functions -var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri); - -/** - * PageMod constructor (exported below). - * @constructor - */ -const ChildPageMod = Class({ - implements: [ - EventTarget, - Disposable, - ], - setup: function PageMod(model) { - merge(this, model); - - // Set listeners on {PageMod} itself, not the underlying worker, - // like `onMessage`, as it'll get piped. - setListeners(this, model); - - function deserializeRules(rules) { - for (let rule of rules) { - yield rule.type == "string" ? rule.value - : new RegExp(rule.pattern, rule.flags); - } - } - - let include = [...deserializeRules(this.include)]; - this.include = Rules(); - this.include.add.apply(this.include, include); - - let exclude = [...deserializeRules(this.exclude)]; - this.exclude = Rules(); - this.exclude.add.apply(this.exclude, exclude); - - if (this.contentStyle || this.contentStyleFile) { - styles.set(this, Style({ - uri: this.contentStyleFile, - source: this.contentStyle - })); - } - - pagemods.set(this.id, this); - this.seenDocuments = new WeakMap(); - - // `applyOnExistingDocuments` has to be called after `pagemods.add()` - // otherwise its calls to `onContent` method won't do anything. - if (has(this.attachTo, 'existing')) - applyOnExistingDocuments(this); - }, - - dispose: function() { - let style = styleFor(this); - if (style) - detach(style); - - for (let i in this.include) - this.include.remove(this.include[i]); - - pagemods.delete(this.id); - } -}); - -function onContentWindow({ target: document }) { - // Return if we have no pagemods - if (pagemods.size === 0) - return; - - let window = document.defaultView; - // XML documents don't have windows, and we don't yet support them. - if (!window) - return; - - // Frame event listeners are bound to the frame the event came from by default - let frame = this; - // We apply only on documents in tabs of Firefox - if (!frame.isTab) - return; - - // When the tab is private, only addons with 'private-browsing' flag in - // their package.json can apply content script to private documents - if (ignoreWindow(window)) - return; - - for (let pagemod of pagemods.values()) { - if (modMatchesURI(pagemod, window.location.href)) - onContent(pagemod, window); - } -} -frames.addEventListener("DOMDocElementInserted", onContentWindow, true); - -function applyOnExistingDocuments (mod) { - for (let frame of frames) { - // Fake a newly created document - let window = frame.content; - // on startup with e10s, contentWindow might not exist yet, - // in which case we will get notified by "document-element-inserted". - if (!window || !window.frames) - return; - let uri = window.location.href; - if (has(mod.attachTo, "top") && modMatchesURI(mod, uri)) - onContent(mod, window); - if (has(mod.attachTo, "frame")) - getFrames(window). - filter(iframe => modMatchesURI(mod, iframe.location.href)). - forEach(frame => onContent(mod, frame)); - } -} - -function createWorker(mod, window) { - let workerId = String(uuid()); - - // Instruct the parent to connect to this worker. Do this first so the parent - // side is connected before the worker attempts to send any messages there - let frame = frames.getFrameForWindow(window.top); - frame.port.emit('sdk/page-mod/worker-create', mod.id, { - id: workerId, - url: window.location.href - }); - - // Create a child worker and notify the parent - let worker = WorkerChild({ - id: workerId, - window: window, - contentScript: mod.contentScript, - contentScriptFile: mod.contentScriptFile, - contentScriptOptions: mod.contentScriptOptions - }); - - once(worker, 'detach', () => worker.destroy()); -} - -function onContent (mod, window) { - let isTopDocument = window.top === window; - // Is a top level document and `top` is not set, ignore - if (isTopDocument && !has(mod.attachTo, "top")) - return; - // Is a frame document and `frame` is not set, ignore - if (!isTopDocument && !has(mod.attachTo, "frame")) - return; - - // ensure we attach only once per document - let seen = mod.seenDocuments; - if (seen.has(window.document)) - return; - seen.set(window.document, true); - - let style = styleFor(mod); - if (style) - attach(style, window); - - // Immediately evaluate content script if the document state is already - // matching contentScriptWhen expectations - if (isMatchingAttachState(mod, window)) { - createWorker(mod, window); - return; - } - - let eventName = getAttachEventType(mod) || 'load'; - domOn(window, eventName, function onReady (e) { - if (e.target.defaultView !== window) - return; - domOff(window, eventName, onReady, true); - createWorker(mod, window); - - // Attaching is asynchronous so if the document is already loaded we will - // miss the pageshow event so send a synthetic one. - if (window.document.readyState == "complete") { - mod.on('attach', worker => { - try { - worker.send('pageshow'); - emit(worker, 'pageshow'); - } - catch (e) { - // This can fail if an earlier attach listener destroyed the worker - } - }); - } - }, true); -} - -function isMatchingAttachState (mod, window) { - let state = window.document.readyState; - return 'start' === mod.contentScriptWhen || - // Is `load` event already dispatched? - 'complete' === state || - // Is DOMContentLoaded already dispatched and waiting for it? - ('ready' === mod.contentScriptWhen && state === 'interactive') -} - -process.port.on('sdk/page-mod/create', (process, model) => { - if (pagemods.has(model.id)) - return; - - new ChildPageMod(model); -}); - -process.port.on('sdk/page-mod/destroy', (process, id) => { - let mod = pagemods.get(id); - if (mod) - mod.destroy(); -}); diff --git a/addon-sdk/source/lib/sdk/content/page-worker.js b/addon-sdk/source/lib/sdk/content/page-worker.js deleted file mode 100644 index e9e741120..000000000 --- a/addon-sdk/source/lib/sdk/content/page-worker.js +++ /dev/null @@ -1,154 +0,0 @@ -/* 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 { frames } = require("../remote/child"); -const { Class } = require("../core/heritage"); -const { Disposable } = require('../core/disposable'); -const { data } = require("../self"); -const { once } = require("../dom/events"); -const { getAttachEventType } = require("./utils"); -const { Rules } = require('../util/rules'); -const { uuid } = require('../util/uuid'); -const { WorkerChild } = require("./worker-child"); -const { Cc, Ci, Cu } = require("chrome"); -const { observe } = require("../event/chrome"); -const { on } = require("../event/core"); - -const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService); - -const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); - -const pages = new Map(); - -const DOC_INSERTED = "document-element-inserted"; - -function isValidURL(page, url) { - return !page.rules || page.rules.matchesAny(url); -} - -const ChildPage = Class({ - implements: [ Disposable ], - setup: function(frame, id, options) { - this.id = id; - this.frame = frame; - this.options = options; - - this.webNav = appShell.createWindowlessBrowser(false); - this.docShell.allowJavascript = this.options.allow.script; - - // Accessing the browser's window forces the initial about:blank document to - // be created before we start listening for notifications - this.contentWindow; - - this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); - - pages.set(this.id, this); - - this.contentURL = options.contentURL; - - if (options.include) { - this.rules = Rules(); - this.rules.add.apply(this.rules, [].concat(options.include)); - } - }, - - dispose: function() { - pages.delete(this.id); - this.webProgress.removeProgressListener(this); - this.webNav.close(); - this.webNav = null; - }, - - attachWorker: function() { - if (!isValidURL(this, this.contentWindow.location.href)) - return; - - this.options.id = uuid().toString(); - this.options.window = this.contentWindow; - this.frame.port.emit("sdk/frame/connect", this.id, { - id: this.options.id, - url: this.contentWindow.document.documentURIObject.spec - }); - new WorkerChild(this.options); - }, - - get docShell() { - return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - }, - - get webProgress() { - return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - }, - - get contentWindow() { - return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - }, - - get contentURL() { - return this.options.contentURL; - }, - set contentURL(url) { - this.options.contentURL = url; - - url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank"; - this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null); - }, - - onLocationChange: function(progress, request, location, flags) { - // Ignore inner-frame events - if (progress != this.webProgress) - return; - // Ignore events that don't change the document - if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) - return; - - let event = getAttachEventType(this.options); - // Attaching at the start of the load is handled by the - // document-element-inserted listener. - if (event == DOC_INSERTED) - return; - - once(this.contentWindow, event, () => { - this.attachWorker(); - }, false); - }, - - QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]) -}); - -on(observe(DOC_INSERTED), "data", ({ target }) => { - let page = Array.from(pages.values()).find(p => p.contentWindow.document === target); - if (!page) - return; - - if (getAttachEventType(page.options) == DOC_INSERTED) - page.attachWorker(); -}); - -frames.port.on("sdk/frame/create", (frame, id, options) => { - new ChildPage(frame, id, options); -}); - -frames.port.on("sdk/frame/set", (frame, id, params) => { - let page = pages.get(id); - if (!page) - return; - - if ("allowScript" in params) - page.docShell.allowJavascript = params.allowScript; - if ("contentURL" in params) - page.contentURL = params.contentURL; -}); - -frames.port.on("sdk/frame/destroy", (frame, id) => { - let page = pages.get(id); - if (!page) - return; - - page.destroy(); -}); diff --git a/addon-sdk/source/lib/sdk/content/sandbox.js b/addon-sdk/source/lib/sdk/content/sandbox.js deleted file mode 100644 index 096ba5c87..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox.js +++ /dev/null @@ -1,426 +0,0 @@ -/* 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 { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { on, off, emit } = require('../event/core'); -const { events } = require('./sandbox/events'); -const { requiresAddonGlobal } = require('./utils'); -const { delay: async } = require('../lang/functional'); -const { Ci, Cu, Cc } = require('chrome'); -const timer = require('../timers'); -const { URL } = require('../url'); -const { sandbox, evaluate, load } = require('../loader/sandbox'); -const { merge } = require('../util/object'); -const { getTabForContentWindowNoShim } = require('../tabs/utils'); -const { getInnerId } = require('../window/utils'); -const { PlainTextConsole } = require('../console/plain-text'); -const { data } = require('../self');const { isChildLoader } = require('../remote/core'); -// WeakMap of sandboxes so we can access private values -const sandboxes = new WeakMap(); - -/* Trick the linker in order to ensure shipping these files in the XPI. - require('./content-worker.js'); - Then, retrieve URL of these files in the XPI: -*/ -var prefix = module.uri.split('sandbox.js')[0]; -const CONTENT_WORKER_URL = prefix + 'content-worker.js'; -const metadata = require('@loader/options').metadata; - -// Fetch additional list of domains to authorize access to for each content -// script. It is stored in manifest `metadata` field which contains -// package.json data. This list is originaly defined by authors in -// `permissions` attribute of their package.json addon file. -const permissions = (metadata && metadata['permissions']) || {}; -const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || []; - -const waiveSecurityMembrane = !!permissions['unsafe-content-script']; - -const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager; -const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]. - getService(Ci.nsIScriptSecurityManager); - -const JS_VERSION = '1.8'; - -// Tests whether this window is loaded in a tab -function isWindowInTab(window) { - if (isChildLoader) { - let { frames } = require('../remote/child'); - let frame = frames.getFrameForWindow(window.top); - return frame && frame.isTab; - } - else { - // The deprecated sync worker API still does everything in the main process - return getTabForContentWindowNoShim(window); - } -} - -const WorkerSandbox = Class({ - implements: [ EventTarget ], - - /** - * Emit a message to the worker content sandbox - */ - emit: function emit(type, ...args) { - // JSON.stringify is buggy with cross-sandbox values, - // it may return "{}" on functions. Use a replacer to match them correctly. - let replacer = (k, v) => - typeof(v) === "function" - ? (type === "console" ? Function.toString.call(v) : void(0)) - : v; - - // Ensure having an asynchronous behavior - async(() => - emitToContent(this, JSON.stringify([type, ...args], replacer)) - ); - }, - - /** - * Synchronous version of `emit`. - * /!\ Should only be used when it is strictly mandatory /!\ - * Doesn't ensure passing only JSON values. - * Mainly used by context-menu in order to avoid breaking it. - */ - emitSync: function emitSync(...args) { - // because the arguments could be also non JSONable values, - // we need to ensure the array instance is created from - // the content's sandbox - return emitToContent(this, new modelFor(this).sandbox.Array(...args)); - }, - - /** - * Configures sandbox and loads content scripts into it. - * @param {Worker} worker - * content worker - */ - initialize: function WorkerSandbox(worker, window) { - let model = {}; - sandboxes.set(this, model); - model.worker = worker; - // We receive a wrapped window, that may be an xraywrapper if it's content - let proto = window; - - // TODO necessary? - // Ensure that `emit` has always the right `this` - this.emit = this.emit.bind(this); - this.emitSync = this.emitSync.bind(this); - - // Use expanded principal for content-script if the content is a - // regular web content for better isolation. - // (This behavior can be turned off for now with the unsafe-content-script - // flag to give addon developers time for making the necessary changes) - // But prevent it when the Worker isn't used for a content script but for - // injecting `addon` object into a Panel scope, for example. - // That's because: - // 1/ It is useless to use multiple domains as the worker is only used - // to communicate with the addon, - // 2/ By using it it would prevent the document to have access to any JS - // value of the worker. As JS values coming from multiple domain principals - // can't be accessed by 'mono-principals' (principal with only one domain). - // Even if this principal is for a domain that is specified in the multiple - // domain principal. - let principals = window; - let wantGlobalProperties = []; - let isSystemPrincipal = secMan.isSystemPrincipal( - window.document.nodePrincipal); - if (!isSystemPrincipal && !requiresAddonGlobal(worker)) { - if (EXPANDED_PRINCIPALS.length > 0) { - // We have to replace XHR constructor of the content document - // with a custom cross origin one, automagically added by platform code: - delete proto.XMLHttpRequest; - wantGlobalProperties.push('XMLHttpRequest'); - } - if (!waiveSecurityMembrane) - principals = EXPANDED_PRINCIPALS.concat(window); - } - - // Create the sandbox and bind it to window in order for content scripts to - // have access to all standard globals (window, document, ...) - let content = sandbox(principals, { - sandboxPrototype: proto, - wantXrays: !requiresAddonGlobal(worker), - wantGlobalProperties: wantGlobalProperties, - wantExportHelpers: true, - sameZoneAs: window, - metadata: { - SDKContentScript: true, - 'inner-window-id': getInnerId(window) - } - }); - model.sandbox = content; - - // We have to ensure that window.top and window.parent are the exact same - // object than window object, i.e. the sandbox global object. But not - // always, in case of iframes, top and parent are another window object. - let top = window.top === window ? content : content.top; - let parent = window.parent === window ? content : content.parent; - merge(content, { - // We need 'this === window === top' to be true in toplevel scope: - get window() { - return content; - }, - get top() { - return top; - }, - get parent() { - return parent; - } - }); - - // Use the Greasemonkey naming convention to provide access to the - // unwrapped window object so the content script can access document - // JavaScript values. - // NOTE: this functionality is experimental and may change or go away - // at any time! - // - // Note that because waivers aren't propagated between origins, we - // need the unsafeWindow getter to live in the sandbox. - var unsafeWindowGetter = - new content.Function('return window.wrappedJSObject || window;'); - Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter}); - - // Load trusted code that will inject content script API. - let ContentWorker = load(content, CONTENT_WORKER_URL); - - // prepare a clean `self.options` - let options = 'contentScriptOptions' in worker ? - JSON.stringify(worker.contentScriptOptions) : - undefined; - - // Then call `inject` method and communicate with this script - // by trading two methods that allow to send events to the other side: - // - `onEvent` called by content script - // - `result.emitToContent` called by addon script - let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker); - let chromeAPI = createChromeAPI(ContentWorker); - let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options); - - // Merge `emitToContent` into our private model of the - // WorkerSandbox so we can communicate with content script - model.emitToContent = result; - - let console = new PlainTextConsole(null, getInnerId(window)); - - // Handle messages send by this script: - setListeners(this, console); - - // Inject `addon` global into target document if document is trusted, - // `addon` in document is equivalent to `self` in content script. - if (requiresAddonGlobal(worker)) { - Object.defineProperty(getUnsafeWindow(window), 'addon', { - value: content.self, - configurable: true - } - ); - } - - // Inject our `console` into target document if worker doesn't have a tab - // (e.g Panel, PageWorker). - // `worker.tab` can't be used because bug 804935. - if (!isWindowInTab(window)) { - let win = getUnsafeWindow(window); - - // export our chrome console to content window, as described here: - // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn - let con = Cu.createObjectIn(win); - - let genPropDesc = function genPropDesc(fun) { - return { enumerable: true, configurable: true, writable: true, - value: console[fun] }; - } - - const properties = { - log: genPropDesc('log'), - info: genPropDesc('info'), - warn: genPropDesc('warn'), - error: genPropDesc('error'), - debug: genPropDesc('debug'), - trace: genPropDesc('trace'), - dir: genPropDesc('dir'), - group: genPropDesc('group'), - groupCollapsed: genPropDesc('groupCollapsed'), - groupEnd: genPropDesc('groupEnd'), - time: genPropDesc('time'), - timeEnd: genPropDesc('timeEnd'), - profile: genPropDesc('profile'), - profileEnd: genPropDesc('profileEnd'), - exception: genPropDesc('exception'), - assert: genPropDesc('assert'), - count: genPropDesc('count'), - table: genPropDesc('table'), - clear: genPropDesc('clear'), - dirxml: genPropDesc('dirxml'), - markTimeline: genPropDesc('markTimeline'), - timeline: genPropDesc('timeline'), - timelineEnd: genPropDesc('timelineEnd'), - timeStamp: genPropDesc('timeStamp'), - }; - - Object.defineProperties(con, properties); - Cu.makeObjectPropsNormal(con); - - win.console = con; - }; - - emit(events, "content-script-before-inserted", { - window: window, - worker: worker - }); - - // The order of `contentScriptFile` and `contentScript` evaluation is - // intentional, so programs can load libraries like jQuery from script URLs - // and use them in scripts. - let contentScriptFile = ('contentScriptFile' in worker) - ? worker.contentScriptFile - : null, - contentScript = ('contentScript' in worker) - ? worker.contentScript - : null; - - if (contentScriptFile) - importScripts.apply(null, [this].concat(contentScriptFile)); - - if (contentScript) { - evaluateIn( - this, - Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript - ); - } - }, - destroy: function destroy(reason) { - if (typeof reason != 'string') - reason = ''; - this.emitSync('event', 'detach', reason); - let model = modelFor(this); - model.sandbox = null - model.worker = null; - }, - -}); - -exports.WorkerSandbox = WorkerSandbox; - -/** - * Imports scripts to the sandbox by reading files under urls and - * evaluating its source. If exception occurs during evaluation - * `'error'` event is emitted on the worker. - * This is actually an analog to the `importScript` method in web - * workers but in our case it's not exposed even though content - * scripts may be able to do it synchronously since IO operation - * takes place in the UI process. - */ -function importScripts (workerSandbox, ...urls) { - let { worker, sandbox } = modelFor(workerSandbox); - for (let i in urls) { - let contentScriptFile = data.url(urls[i]); - - try { - let uri = URL(contentScriptFile); - if (uri.scheme === 'resource') - load(sandbox, String(uri)); - else - throw Error('Unsupported `contentScriptFile` url: ' + String(uri)); - } - catch(e) { - emit(worker, 'error', e); - } - } -} - -function setListeners (workerSandbox, console) { - let { worker } = modelFor(workerSandbox); - // console.xxx calls - workerSandbox.on('console', function consoleListener (kind, ...args) { - console[kind].apply(console, args); - }); - - // self.postMessage calls - workerSandbox.on('message', function postMessage(data) { - // destroyed? - if (worker) - emit(worker, 'message', data); - }); - - // self.port.emit calls - workerSandbox.on('event', function portEmit (...eventArgs) { - // If not destroyed, emit event information to worker - // `eventArgs` has the event name as first element, - // and remaining elements are additional arguments to pass - if (worker) - emit.apply(null, [worker.port].concat(eventArgs)); - }); - - // unwrap, recreate and propagate async Errors thrown from content-script - workerSandbox.on('error', function onError({instanceOfError, value}) { - if (worker) { - let error = value; - if (instanceOfError) { - error = new Error(value.message, value.fileName, value.lineNumber); - error.stack = value.stack; - error.name = value.name; - } - emit(worker, 'error', error); - } - }); -} - -/** - * Evaluates code in the sandbox. - * @param {String} code - * JavaScript source to evaluate. - * @param {String} [filename='javascript:' + code] - * Name of the file - */ -function evaluateIn (workerSandbox, code, filename) { - let { worker, sandbox } = modelFor(workerSandbox); - try { - evaluate(sandbox, code, filename || 'javascript:' + code); - } - catch(e) { - emit(worker, 'error', e); - } -} - -/** - * Method called by the worker sandbox when it needs to send a message - */ -function onContentEvent (workerSandbox, args) { - // As `emit`, we ensure having an asynchronous behavior - async(function () { - // We emit event to chrome/addon listeners - emit.apply(null, [workerSandbox].concat(JSON.parse(args))); - }); -} - - -function modelFor (workerSandbox) { - return sandboxes.get(workerSandbox); -} - -function getUnsafeWindow (win) { - return win.wrappedJSObject || win; -} - -function emitToContent (workerSandbox, args) { - return modelFor(workerSandbox).emitToContent(args); -} - -function createChromeAPI (scope) { - return Cu.cloneInto({ - timers: { - setTimeout: timer.setTimeout.bind(timer), - setInterval: timer.setInterval.bind(timer), - clearTimeout: timer.clearTimeout.bind(timer), - clearInterval: timer.clearInterval.bind(timer), - }, - sandbox: { - evaluate: evaluate, - }, - }, scope, {cloneFunctions: true}); -} diff --git a/addon-sdk/source/lib/sdk/content/sandbox/events.js b/addon-sdk/source/lib/sdk/content/sandbox/events.js deleted file mode 100644 index d6f7eb004..000000000 --- a/addon-sdk/source/lib/sdk/content/sandbox/events.js +++ /dev/null @@ -1,12 +0,0 @@ -/* 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": "experimental" -}; - -const events = {}; -exports.events = events; diff --git a/addon-sdk/source/lib/sdk/content/tab-events.js b/addon-sdk/source/lib/sdk/content/tab-events.js deleted file mode 100644 index 9e244a853..000000000 --- a/addon-sdk/source/lib/sdk/content/tab-events.js +++ /dev/null @@ -1,58 +0,0 @@ -/* 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 system = require('sdk/system/events'); -const { frames } = require('sdk/remote/child'); -const { WorkerChild } = require('sdk/content/worker-child'); - -// map observer topics to tab event names -const EVENTS = { - 'content-document-global-created': 'create', - 'chrome-document-global-created': 'create', - 'content-document-interactive': 'ready', - 'chrome-document-interactive': 'ready', - 'content-document-loaded': 'load', - 'chrome-document-loaded': 'load', -// 'content-page-shown': 'pageshow', // bug 1024105 -} - -function topicListener({ subject, type }) { - // NOTE detect the window from the subject: - // - on *-global-created the subject is the window - // - in the other cases it is the document object - let window = subject instanceof Ci.nsIDOMWindow ? subject : subject.defaultView; - if (!window){ - return; - } - let frame = frames.getFrameForWindow(window); - if (frame) { - let readyState = frame.content.document.readyState; - frame.port.emit('sdk/tab/event', EVENTS[type], { readyState }); - } -} - -for (let topic in EVENTS) - system.on(topic, topicListener, true); - -// bug 1024105 - content-page-shown notification doesn't pass persisted param -function eventListener({target, type, persisted}) { - let frame = this; - if (target === frame.content.document) { - frame.port.emit('sdk/tab/event', type, persisted); - } -} -frames.addEventListener('pageshow', eventListener, true); - -frames.port.on('sdk/tab/attach', (frame, options) => { - options.window = frame.content; - new WorkerChild(options); -}); - -// Forward the existent frames's readyState. -for (let frame of frames) { - let readyState = frame.content.document.readyState; - frame.port.emit('sdk/tab/event', 'init', { readyState }); -} diff --git a/addon-sdk/source/lib/sdk/content/thumbnail.js b/addon-sdk/source/lib/sdk/content/thumbnail.js deleted file mode 100644 index 783615fc6..000000000 --- a/addon-sdk/source/lib/sdk/content/thumbnail.js +++ /dev/null @@ -1,51 +0,0 @@ -/* 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 { Cc, Ci, Cu } = require('chrome'); -const AppShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); - -const NS = 'http://www.w3.org/1999/xhtml'; -const COLOR = 'rgb(255,255,255)'; - -/** - * Creates canvas element with a thumbnail of the passed window. - * @param {Window} window - * @returns {Element} - */ -function getThumbnailCanvasForWindow(window) { - let aspectRatio = 0.5625; // 16:9 - let thumbnail = AppShellService.hiddenDOMWindow.document - .createElementNS(NS, 'canvas'); - thumbnail.mozOpaque = true; - thumbnail.width = Math.ceil(window.screen.availWidth / 5.75); - thumbnail.height = Math.round(thumbnail.width * aspectRatio); - let ctx = thumbnail.getContext('2d'); - let snippetWidth = window.innerWidth * .6; - let scale = thumbnail.width / snippetWidth; - ctx.scale(scale, scale); - ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth, - snippetWidth * aspectRatio, COLOR); - return thumbnail; -} -exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow; - -/** - * Creates Base64 encoded data URI of the thumbnail for the passed window. - * @param {Window} window - * @returns {String} - */ -exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) { - return getThumbnailCanvasForWindow(window).toDataURL() -}; - -// default 80x45 blank when not available -exports.BLANK = 'data:image/png;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+ - 'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC'; diff --git a/addon-sdk/source/lib/sdk/content/utils.js b/addon-sdk/source/lib/sdk/content/utils.js deleted file mode 100644 index 90995a614..000000000 --- a/addon-sdk/source/lib/sdk/content/utils.js +++ /dev/null @@ -1,105 +0,0 @@ -/* 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' -}; - -var { merge } = require('../util/object'); -var { data } = require('../self'); -var assetsURI = data.url(); -var isArray = Array.isArray; -var method = require('../../method/core'); -var { uuid } = require('../util/uuid'); - -const isAddonContent = ({ contentURL }) => - contentURL && data.url(contentURL).startsWith(assetsURI); - -exports.isAddonContent = isAddonContent; - -function hasContentScript({ contentScript, contentScriptFile }) { - return (isArray(contentScript) ? contentScript.length > 0 : - !!contentScript) || - (isArray(contentScriptFile) ? contentScriptFile.length > 0 : - !!contentScriptFile); -} -exports.hasContentScript = hasContentScript; - -function requiresAddonGlobal(model) { - return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model)); -} -exports.requiresAddonGlobal = requiresAddonGlobal; - -function getAttachEventType(model) { - if (!model) return null; - let when = model.contentScriptWhen; - return requiresAddonGlobal(model) ? 'document-element-inserted' : - when === 'start' ? 'document-element-inserted' : - when === 'ready' ? 'DOMContentLoaded' : - when === 'end' ? 'load' : - null; -} -exports.getAttachEventType = getAttachEventType; - -var attach = method('worker-attach'); -exports.attach = attach; - -var connect = method('worker-connect'); -exports.connect = connect; - -var detach = method('worker-detach'); -exports.detach = detach; - -var destroy = method('worker-destroy'); -exports.destroy = destroy; - -function WorkerHost (workerFor) { - // Define worker properties that just proxy to underlying worker - return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) { - // Use descriptor properties instead so we can call - // the worker function in the context of the worker so we - // don't have to create new functions with `fn.bind(worker)` - let descriptorProp = { - value: function (...args) { - let worker = workerFor(this); - return worker[name].apply(worker, args); - } - }; - - let accessorProp = { - get: function () { return workerFor(this)[name]; }, - set: function (value) { workerFor(this)[name] = value; } - }; - - Object.defineProperty(proto, name, merge({ - enumerable: true, - configurable: false, - }, isDescriptor(name) ? descriptorProp : accessorProp)); - return proto; - }, {}); - - function isDescriptor (prop) { - return ~['postMessage'].indexOf(prop); - } -} -exports.WorkerHost = WorkerHost; - -function makeChildOptions(options) { - function makeStringArray(arrayOrValue) { - if (!arrayOrValue) - return []; - return [].concat(arrayOrValue).map(String); - } - - return { - id: String(uuid()), - contentScript: makeStringArray(options.contentScript), - contentScriptFile: makeStringArray(options.contentScriptFile), - contentScriptOptions: options.contentScriptOptions ? - JSON.stringify(options.contentScriptOptions) : - null, - } -} -exports.makeChildOptions = makeChildOptions; diff --git a/addon-sdk/source/lib/sdk/content/worker-child.js b/addon-sdk/source/lib/sdk/content/worker-child.js deleted file mode 100644 index dbf65a933..000000000 --- a/addon-sdk/source/lib/sdk/content/worker-child.js +++ /dev/null @@ -1,158 +0,0 @@ -/* 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 { merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { emit } = require('../event/core'); -const { EventTarget } = require('../event/target'); -const { getInnerId, getByInnerId } = require('../window/utils'); -const { instanceOf, isObject } = require('../lang/type'); -const system = require('../system/events'); -const { when } = require('../system/unload'); -const { WorkerSandbox } = require('./sandbox'); -const { Ci } = require('chrome'); -const { process, frames } = require('../remote/child'); - -const EVENTS = { - 'chrome-page-shown': 'pageshow', - 'content-page-shown': 'pageshow', - 'chrome-page-hidden': 'pagehide', - 'content-page-hidden': 'pagehide', - 'inner-window-destroyed': 'detach', -} - -// The parent Worker must have been created (or an async message sent to spawn -// its creation) before creating the WorkerChild or messages from the content -// script to the parent will get lost. -const WorkerChild = Class({ - implements: [EventTarget], - - initialize(options) { - merge(this, options); - keepAlive.set(this.id, this); - - this.windowId = getInnerId(this.window); - if (this.contentScriptOptions) - this.contentScriptOptions = JSON.parse(this.contentScriptOptions); - - this.port = EventTarget(); - this.port.on('*', this.send.bind(this, 'event')); - this.on('*', this.send.bind(this)); - - this.observe = this.observe.bind(this); - - for (let topic in EVENTS) - system.on(topic, this.observe); - - this.receive = this.receive.bind(this); - process.port.on('sdk/worker/message', this.receive); - - this.sandbox = WorkerSandbox(this, this.window); - - // If the document has an unexpected readyState, its worker-child instance is initialized - // as frozen until one of the known readyState is reached. - let initialDocumentReadyState = this.window.document.readyState; - this.frozen = [ - "loading", "interactive", "complete" - ].includes(initialDocumentReadyState) ? false : true; - - if (this.frozen) { - console.warn("SDK worker-child started as frozen on unexpected initial document.readyState", { - initialDocumentReadyState, windowLocation: this.window.location.href, - }); - } - - this.frozenMessages = []; - this.on('pageshow', () => { - this.frozen = false; - this.frozenMessages.forEach(args => this.sandbox.emit(...args)); - this.frozenMessages = []; - }); - this.on('pagehide', () => { - this.frozen = true; - }); - }, - - // messages - receive(process, id, args) { - if (id !== this.id) - return; - args = JSON.parse(args); - - if (this.frozen) - this.frozenMessages.push(args); - else - this.sandbox.emit(...args); - - if (args[0] === 'detach') - this.destroy(args[1]); - }, - - send(...args) { - process.port.emit('sdk/worker/event', this.id, JSON.stringify(args, exceptions)); - }, - - // notifications - observe({ type, subject }) { - if (!this.sandbox) - return; - - if (subject.defaultView && getInnerId(subject.defaultView) === this.windowId) { - this.sandbox.emitSync(EVENTS[type]); - emit(this, EVENTS[type]); - } - - if (type === 'inner-window-destroyed' && - subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.windowId) { - this.destroy(); - } - }, - - get frame() { - return frames.getFrameForWindow(this.window.top); - }, - - // detach/destroy: unload and release the sandbox - destroy(reason) { - if (!this.sandbox) - return; - - for (let topic in EVENTS) - system.off(topic, this.observe); - process.port.off('sdk/worker/message', this.receive); - - this.sandbox.destroy(reason); - this.sandbox = null; - keepAlive.delete(this.id); - - this.send('detach'); - } -}) -exports.WorkerChild = WorkerChild; - -// Error instances JSON poorly -function exceptions(key, value) { - if (!isObject(value) || !instanceOf(value, Error)) - return value; - let _errorType = value.constructor.name; - let { message, fileName, lineNumber, stack, name } = value; - return { _errorType, message, fileName, lineNumber, stack, name }; -} - -// workers for windows in this tab -var keepAlive = new Map(); - -process.port.on('sdk/worker/create', (process, options, cpows) => { - options.window = cpows.window; - let worker = new WorkerChild(options); - - let frame = frames.getFrameForWindow(options.window.top); - frame.port.emit('sdk/worker/connect', options.id, options.window.location.href); -}); - -when(reason => { - for (let worker of keepAlive.values()) - worker.destroy(reason); -}); diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js deleted file mode 100644 index 39b940a88..000000000 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ /dev/null @@ -1,180 +0,0 @@ -/* 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 { emit } = require('../event/core'); -const { omit, merge } = require('../util/object'); -const { Class } = require('../core/heritage'); -const { method } = require('../lang/functional'); -const { getInnerId } = require('../window/utils'); -const { EventTarget } = require('../event/target'); -const { isPrivate } = require('../private-browsing/utils'); -const { getTabForBrowser, getTabForContentWindowNoShim, getBrowserForTab } = require('../tabs/utils'); -const { attach, connect, detach, destroy, makeChildOptions } = require('./utils'); -const { ensure } = require('../system/unload'); -const { on: observe } = require('../system/events'); -const { Ci, Cu } = require('chrome'); -const { modelFor: tabFor } = require('sdk/model/core'); -const { remoteRequire, processes, frames } = require('../remote/parent'); -remoteRequire('sdk/content/worker-child'); - -const workers = new WeakMap(); -var modelFor = (worker) => workers.get(worker); - -const ERR_DESTROYED = "Couldn't find the worker to receive this message. " + - "The script may not be initialized yet, or may already have been unloaded."; - -// a handle for communication between content script and addon code -const Worker = Class({ - implements: [EventTarget], - - initialize(options = {}) { - ensure(this, 'detach'); - - let model = { - attached: false, - destroyed: false, - earlyEvents: [], // fired before worker was attached - frozen: true, // document is not yet active - options, - }; - workers.set(this, model); - - this.on('detach', this.detach); - EventTarget.prototype.initialize.call(this, options); - - this.receive = this.receive.bind(this); - - this.port = EventTarget(); - this.port.emit = this.send.bind(this, 'event'); - this.postMessage = this.send.bind(this, 'message'); - - if ('window' in options) { - let window = options.window; - delete options.window; - attach(this, window); - } - }, - - // messages - receive(process, id, args) { - let model = modelFor(this); - if (id !== model.id || !model.attached) - return; - args = JSON.parse(args); - if (model.destroyed && args[0] != 'detach') - return; - - if (args[0] === 'event') - emit(this.port, ...args.slice(1)) - else - emit(this, ...args); - }, - - send(...args) { - let model = modelFor(this); - if (model.destroyed && args[0] !== 'detach') - throw new Error(ERR_DESTROYED); - - if (!model.attached) { - model.earlyEvents.push(args); - return; - } - - processes.port.emit('sdk/worker/message', model.id, JSON.stringify(args)); - }, - - // properties - get url() { - let { url } = modelFor(this); - return url; - }, - - get contentURL() { - return this.url; - }, - - get tab() { - require('sdk/tabs'); - let { frame } = modelFor(this); - if (!frame) - return null; - let rawTab = getTabForBrowser(frame.frameElement); - return rawTab && tabFor(rawTab); - }, - - toString: () => '[object Worker]', - - detach: method(detach), - destroy: method(destroy), -}) -exports.Worker = Worker; - -attach.define(Worker, function(worker, window) { - let model = modelFor(worker); - if (model.attached) - detach(worker); - - let childOptions = makeChildOptions(model.options); - processes.port.emitCPOW('sdk/worker/create', [childOptions], { window }); - - let listener = (frame, id, url) => { - if (id != childOptions.id) - return; - frames.port.off('sdk/worker/connect', listener); - connect(worker, frame, { id, url }); - }; - frames.port.on('sdk/worker/connect', listener); -}); - -connect.define(Worker, function(worker, frame, { id, url }) { - let model = modelFor(worker); - if (model.attached) - detach(worker); - - model.id = id; - model.frame = frame; - model.url = url; - - // Messages from content -> chrome come through the process message manager - // since that lives longer than the frame message manager - processes.port.on('sdk/worker/event', worker.receive); - - model.attached = true; - model.destroyed = false; - model.frozen = false; - - model.earlyEvents.forEach(args => worker.send(...args)); - model.earlyEvents = []; - emit(worker, 'attach'); -}); - -// unload and release the child worker, release window reference -detach.define(Worker, function(worker) { - let model = modelFor(worker); - if (!model.attached) - return; - - processes.port.off('sdk/worker/event', worker.receive); - model.attached = false; - model.destroyed = true; - emit(worker, 'detach'); -}); - -isPrivate.define(Worker, ({ tab }) => isPrivate(tab)); - -// Something in the parent side has destroyed the worker, tell the child to -// detach, the child will respond when it has detached -destroy.define(Worker, function(worker, reason) { - let model = modelFor(worker); - model.destroyed = true; - if (!model.attached) - return; - - worker.send('detach', reason); -}); diff --git a/addon-sdk/source/lib/sdk/context-menu.js b/addon-sdk/source/lib/sdk/context-menu.js deleted file mode 100644 index 004c642d4..000000000 --- a/addon-sdk/source/lib/sdk/context-menu.js +++ /dev/null @@ -1,1188 +0,0 @@ -/* 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": "stable", - "engines": { - // TODO Fennec support Bug 788334 - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Class, mix } = require("./core/heritage"); -const { addCollectionProperty } = require("./util/collection"); -const { ns } = require("./core/namespace"); -const { validateOptions, getTypeOf } = require("./deprecated/api-utils"); -const { URL, isValidURI } = require("./url"); -const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils"); -const { isBrowser, getInnerId } = require("./window/utils"); -const { MatchPattern } = require("./util/match-pattern"); -const { EventTarget } = require("./event/target"); -const { emit } = require('./event/core'); -const { when } = require('./system/unload'); -const { contract: loaderContract } = require('./content/loader'); -const { omit } = require('./util/object'); -const self = require('./self') -const { remoteRequire, processes } = require('./remote/parent'); -remoteRequire('sdk/content/context-menu'); - -// All user items we add have this class. -const ITEM_CLASS = "addon-context-menu-item"; - -// Items in the top-level context menu also have this class. -const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel"; - -// Items in the overflow submenu also have this class. -const OVERFLOW_ITEM_CLASS = "addon-context-menu-item-overflow"; - -// The class of the menu separator that separates standard context menu items -// from our user items. -const SEPARATOR_CLASS = "addon-context-menu-separator"; - -// If more than this number of items are added to the context menu, all items -// overflow into a "Jetpack" submenu. -const OVERFLOW_THRESH_DEFAULT = 10; -const OVERFLOW_THRESH_PREF = - "extensions.addon-sdk.context-menu.overflowThreshold"; - -// The label of the overflow sub-xul:menu. -// -// TODO: Localize these. -const OVERFLOW_MENU_LABEL = "Add-ons"; -const OVERFLOW_MENU_ACCESSKEY = "A"; - -// The class of the overflow sub-xul:menu. -const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu"; - -// The class of the overflow submenu's xul:menupopup. -const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup"; - -// Holds private properties for API objects -var internal = ns(); - -// A little hacky but this is the last process ID that last opened the context -// menu -var lastContextProcessId = null; - -var uuidModule = require('./util/uuid'); -function uuid() { - return uuidModule.uuid().toString(); -} - -function getScheme(spec) { - try { - return URL(spec).scheme; - } - catch(e) { - return null; - } -} - -var Context = Class({ - initialize: function() { - internal(this).id = uuid(); - }, - - // Returns the node that made this context current - adjustPopupNode: function adjustPopupNode(popupNode) { - return popupNode; - }, - - // Returns whether this context is current for the current node - isCurrent: function isCurrent(state) { - return state; - } -}); - -// Matches when the context-clicked node doesn't have any of -// NON_PAGE_CONTEXT_ELTS in its ancestors -var PageContext = Class({ - extends: Context, - - serialize: function() { - return { - id: internal(this).id, - type: "PageContext", - args: [] - } - } -}); -exports.PageContext = PageContext; - -// Matches when there is an active selection in the window -var SelectionContext = Class({ - extends: Context, - - serialize: function() { - return { - id: internal(this).id, - type: "SelectionContext", - args: [] - } - } -}); -exports.SelectionContext = SelectionContext; - -// Matches when the context-clicked node or any of its ancestors matches the -// selector given -var SelectorContext = Class({ - extends: Context, - - initialize: function initialize(selector) { - Context.prototype.initialize.call(this); - let options = validateOptions({ selector: selector }, { - selector: { - is: ["string"], - msg: "selector must be a string." - } - }); - internal(this).selector = options.selector; - }, - - serialize: function() { - return { - id: internal(this).id, - type: "SelectorContext", - args: [internal(this).selector] - } - } -}); -exports.SelectorContext = SelectorContext; - -// Matches when the page url matches any of the patterns given -var URLContext = Class({ - extends: Context, - - initialize: function initialize(patterns) { - Context.prototype.initialize.call(this); - patterns = Array.isArray(patterns) ? patterns : [patterns]; - - try { - internal(this).patterns = patterns.map(p => new MatchPattern(p)); - } - catch (err) { - throw new Error("Patterns must be a string, regexp or an array of " + - "strings or regexps: " + err); - } - }, - - isCurrent: function isCurrent(url) { - return internal(this).patterns.some(p => p.test(url)); - }, - - serialize: function() { - return { - id: internal(this).id, - type: "URLContext", - args: [] - } - } -}); -exports.URLContext = URLContext; - -// Matches when the user-supplied predicate returns true -var PredicateContext = Class({ - extends: Context, - - initialize: function initialize(predicate) { - Context.prototype.initialize.call(this); - let options = validateOptions({ predicate: predicate }, { - predicate: { - is: ["function"], - msg: "predicate must be a function." - } - }); - internal(this).predicate = options.predicate; - }, - - isCurrent: function isCurrent(state) { - return internal(this).predicate(state); - }, - - serialize: function() { - return { - id: internal(this).id, - type: "PredicateContext", - args: [] - } - } -}); -exports.PredicateContext = PredicateContext; - -function removeItemFromArray(array, item) { - return array.filter(i => i !== item); -} - -// Converts anything that isn't false, null or undefined into a string -function stringOrNull(val) { - return val ? String(val) : val; -} - -// Shared option validation rules for Item, Menu, and Separator -var baseItemRules = { - parentMenu: { - is: ["object", "undefined"], - ok: function (v) { - if (!v) - return true; - return (v instanceof ItemContainer) || (v instanceof Menu); - }, - msg: "parentMenu must be a Menu or not specified." - }, - context: { - is: ["undefined", "object", "array"], - ok: function (v) { - if (!v) - return true; - let arr = Array.isArray(v) ? v : [v]; - return arr.every(o => o instanceof Context); - }, - msg: "The 'context' option must be a Context object or an array of " + - "Context objects." - }, - onMessage: { - is: ["function", "undefined"] - }, - contentScript: loaderContract.rules.contentScript, - contentScriptFile: loaderContract.rules.contentScriptFile -}; - -var labelledItemRules = mix(baseItemRules, { - label: { - map: stringOrNull, - is: ["string"], - ok: v => !!v, - msg: "The item must have a non-empty string label." - }, - accesskey: { - map: stringOrNull, - is: ["string", "undefined", "null"], - ok: (v) => { - if (!v) { - return true; - } - return typeof v == "string" && v.length === 1; - }, - msg: "The item must have a single character accesskey, or no accesskey." - }, - image: { - map: stringOrNull, - is: ["string", "undefined", "null"], - ok: function (url) { - if (!url) - return true; - return isValidURI(url); - }, - msg: "Image URL validation failed" - } -}); - -// Additional validation rules for Item -var itemRules = mix(labelledItemRules, { - data: { - map: stringOrNull, - is: ["string", "undefined", "null"] - } -}); - -// Additional validation rules for Menu -var menuRules = mix(labelledItemRules, { - items: { - is: ["array", "undefined"], - ok: function (v) { - if (!v) - return true; - return v.every(function (item) { - return item instanceof BaseItem; - }); - }, - msg: "items must be an array, and each element in the array must be an " + - "Item, Menu, or Separator." - } -}); - -// Returns true if any contexts match. If there are no contexts then a -// PageContext is tested instead -function hasMatchingContext(contexts, addonInfo) { - for (let context of contexts) { - if (!(internal(context).id in addonInfo.contextStates)) { - console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules."); - return false; - } - if (!context.isCurrent(addonInfo.contextStates[internal(context).id])) - return false; - } - - return true; -} - -// Tests whether an item should be visible or not based on its contexts and -// content scripts -function isItemVisible(item, addonInfo, usePageWorker) { - if (!item.context.length) { - if (!addonInfo.hasWorker) - return usePageWorker ? addonInfo.pageContext : true; - } - - if (!hasMatchingContext(item.context, addonInfo)) - return false; - - let context = addonInfo.workerContext; - if (typeof(context) === "string" && context != "") - item.label = context; - - return !!context; -} - -// Called when an item is clicked to send out click events to the content -// scripts -function itemActivated(item, clickedNode) { - let items = [internal(item).id]; - let data = item.data; - - while (item.parentMenu) { - item = item.parentMenu; - items.push(internal(item).id); - } - - let process = processes.getById(lastContextProcessId); - if (process) - process.port.emit('sdk/contextmenu/activateitems', items, data); -} - -function serializeItem(item) { - return { - id: internal(item).id, - contexts: item.context.map(c => c.serialize()), - contentScript: item.contentScript, - contentScriptFile: item.contentScriptFile, - }; -} - -// All things that appear in the context menu extend this -var BaseItem = Class({ - initialize: function initialize() { - internal(this).id = uuid(); - - internal(this).contexts = []; - if ("context" in internal(this).options && internal(this).options.context) { - let contexts = internal(this).options.context; - if (Array.isArray(contexts)) { - for (let context of contexts) - internal(this).contexts.push(context); - } - else { - internal(this).contexts.push(contexts); - } - } - - let parentMenu = internal(this).options.parentMenu; - if (!parentMenu) - parentMenu = contentContextMenu; - - parentMenu.addItem(this); - - Object.defineProperty(this, "contentScript", { - enumerable: true, - value: internal(this).options.contentScript - }); - - // Resolve URIs here as tests may have overriden self - let files = internal(this).options.contentScriptFile; - if (files) { - if (!Array.isArray(files)) - files = [files]; - files = files.map(self.data.url); - } - internal(this).options.contentScriptFile = files; - Object.defineProperty(this, "contentScriptFile", { - enumerable: true, - value: internal(this).options.contentScriptFile - }); - - // Notify all frames of this new item - sendItems([serializeItem(this)]); - }, - - destroy: function destroy() { - if (internal(this).destroyed) - return; - - // Tell all existing frames that this item has been destroyed - processes.port.emit("sdk/contextmenu/destroyitems", [internal(this).id]); - - if (this.parentMenu) - this.parentMenu.removeItem(this); - - internal(this).destroyed = true; - }, - - get context() { - let contexts = internal(this).contexts.slice(0); - contexts.add = (context) => { - internal(this).contexts.push(context); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }; - contexts.remove = (context) => { - internal(this).contexts = internal(this).contexts.filter(c => { - return c != context; - }); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }; - return contexts; - }, - - set context(val) { - internal(this).contexts = val.slice(0); - // Notify all frames that this item has changed - sendItems([serializeItem(this)]); - }, - - get parentMenu() { - return internal(this).parentMenu; - }, -}); - -function workerMessageReceived(process, id, args) { - if (internal(this).id != id) - return; - - emit(this, ...JSON.parse(args)); -} - -// All things that have a label on the context menu extend this -var LabelledItem = Class({ - extends: BaseItem, - implements: [ EventTarget ], - - initialize: function initialize(options) { - BaseItem.prototype.initialize.call(this); - EventTarget.prototype.initialize.call(this, options); - - internal(this).messageListener = workerMessageReceived.bind(this); - processes.port.on('sdk/worker/event', internal(this).messageListener); - }, - - destroy: function destroy() { - if (internal(this).destroyed) - return; - - processes.port.off('sdk/worker/event', internal(this).messageListener); - - BaseItem.prototype.destroy.call(this); - }, - - get label() { - return internal(this).options.label; - }, - - set label(val) { - internal(this).options.label = val; - - MenuManager.updateItem(this); - }, - - get accesskey() { - return internal(this).options.accesskey; - }, - - set accesskey(val) { - internal(this).options.accesskey = val; - - MenuManager.updateItem(this); - }, - - get image() { - return internal(this).options.image; - }, - - set image(val) { - internal(this).options.image = val; - - MenuManager.updateItem(this); - }, - - get data() { - return internal(this).options.data; - }, - - set data(val) { - internal(this).options.data = val; - } -}); - -var Item = Class({ - extends: LabelledItem, - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, itemRules); - - LabelledItem.prototype.initialize.call(this, options); - }, - - toString: function toString() { - return "[object Item \"" + this.label + "\"]"; - }, - - get data() { - return internal(this).options.data; - }, - - set data(val) { - internal(this).options.data = val; - - MenuManager.updateItem(this); - }, -}); -exports.Item = Item; - -var ItemContainer = Class({ - initialize: function initialize() { - internal(this).children = []; - }, - - destroy: function destroy() { - // Destroys the entire hierarchy - for (let item of internal(this).children) - item.destroy(); - }, - - addItem: function addItem(item) { - let oldParent = item.parentMenu; - - // Don't just call removeItem here as that would remove the corresponding - // UI element which is more costly than just moving it to the right place - if (oldParent) - internal(oldParent).children = removeItemFromArray(internal(oldParent).children, item); - - let after = null; - let children = internal(this).children; - if (children.length > 0) - after = children[children.length - 1]; - - children.push(item); - internal(item).parentMenu = this; - - // If there was an old parent then we just have to move the item, otherwise - // it needs to be created - if (oldParent) - MenuManager.moveItem(item, after); - else - MenuManager.createItem(item, after); - }, - - removeItem: function removeItem(item) { - // If the item isn't a child of this menu then ignore this call - if (item.parentMenu !== this) - return; - - MenuManager.removeItem(item); - - internal(this).children = removeItemFromArray(internal(this).children, item); - internal(item).parentMenu = null; - }, - - get items() { - return internal(this).children.slice(0); - }, - - set items(val) { - // Validate the arguments before making any changes - if (!Array.isArray(val)) - throw new Error(menuOptionRules.items.msg); - - for (let item of val) { - if (!(item instanceof BaseItem)) - throw new Error(menuOptionRules.items.msg); - } - - // Remove the old items and add the new ones - for (let item of internal(this).children) - this.removeItem(item); - - for (let item of val) - this.addItem(item); - }, -}); - -var Menu = Class({ - extends: LabelledItem, - implements: [ItemContainer], - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, menuRules); - - LabelledItem.prototype.initialize.call(this, options); - ItemContainer.prototype.initialize.call(this); - - if (internal(this).options.items) { - for (let item of internal(this).options.items) - this.addItem(item); - } - }, - - destroy: function destroy() { - ItemContainer.prototype.destroy.call(this); - LabelledItem.prototype.destroy.call(this); - }, - - toString: function toString() { - return "[object Menu \"" + this.label + "\"]"; - }, -}); -exports.Menu = Menu; - -var Separator = Class({ - extends: BaseItem, - - initialize: function initialize(options) { - internal(this).options = validateOptions(options, baseItemRules); - - BaseItem.prototype.initialize.call(this); - }, - - toString: function toString() { - return "[object Separator]"; - } -}); -exports.Separator = Separator; - -// Holds items for the content area context menu -var contentContextMenu = ItemContainer(); -exports.contentContextMenu = contentContextMenu; - -function getContainerItems(container) { - let items = []; - for (let item of internal(container).children) { - items.push(serializeItem(item)); - if (item instanceof Menu) - items = items.concat(getContainerItems(item)); - } - return items; -} - -// Notify all frames of these new or changed items -function sendItems(items) { - processes.port.emit("sdk/contextmenu/createitems", items); -} - -// Called when a new process is created and needs to get the current list of items -function remoteItemRequest(process) { - let items = getContainerItems(contentContextMenu); - if (items.length == 0) - return; - - process.port.emit("sdk/contextmenu/createitems", items); -} -processes.forEvery(remoteItemRequest); - -when(function() { - contentContextMenu.destroy(); -}); - -// App specific UI code lives here, it should handle populating the context -// menu and passing clicks etc. through to the items. - -function countVisibleItems(nodes) { - return Array.reduce(nodes, function(sum, node) { - return node.hidden ? sum : sum + 1; - }, 0); -} - -var MenuWrapper = Class({ - initialize: function initialize(winWrapper, items, contextMenu) { - this.winWrapper = winWrapper; - this.window = winWrapper.window; - this.items = items; - this.contextMenu = contextMenu; - this.populated = false; - this.menuMap = new Map(); - - // updateItemVisibilities will run first, updateOverflowState will run after - // all other instances of this module have run updateItemVisibilities - this._updateItemVisibilities = this.updateItemVisibilities.bind(this); - this.contextMenu.addEventListener("popupshowing", this._updateItemVisibilities, true); - this._updateOverflowState = this.updateOverflowState.bind(this); - this.contextMenu.addEventListener("popupshowing", this._updateOverflowState, false); - }, - - destroy: function destroy() { - this.contextMenu.removeEventListener("popupshowing", this._updateOverflowState, false); - this.contextMenu.removeEventListener("popupshowing", this._updateItemVisibilities, true); - - if (!this.populated) - return; - - // If we're getting unloaded at runtime then we must remove all the - // generated XUL nodes - let oldParent = null; - for (let item of internal(this.items).children) { - let xulNode = this.getXULNodeForItem(item); - oldParent = xulNode.parentNode; - oldParent.removeChild(xulNode); - } - - if (oldParent) - this.onXULRemoved(oldParent); - }, - - get separator() { - return this.contextMenu.querySelector("." + SEPARATOR_CLASS); - }, - - get overflowMenu() { - return this.contextMenu.querySelector("." + OVERFLOW_MENU_CLASS); - }, - - get overflowPopup() { - return this.contextMenu.querySelector("." + OVERFLOW_POPUP_CLASS); - }, - - get topLevelItems() { - return this.contextMenu.querySelectorAll("." + TOPLEVEL_ITEM_CLASS); - }, - - get overflowItems() { - return this.contextMenu.querySelectorAll("." + OVERFLOW_ITEM_CLASS); - }, - - getXULNodeForItem: function getXULNodeForItem(item) { - return this.menuMap.get(item); - }, - - // Recurses through the item hierarchy creating XUL nodes for everything - populate: function populate(menu) { - for (let i = 0; i < internal(menu).children.length; i++) { - let item = internal(menu).children[i]; - let after = i === 0 ? null : internal(menu).children[i - 1]; - this.createItem(item, after); - - if (item instanceof Menu) - this.populate(item); - } - }, - - // Recurses through the menu setting the visibility of items. Returns true - // if any of the items in this menu were visible - setVisibility: function setVisibility(menu, addonInfo, usePageWorker) { - let anyVisible = false; - - for (let item of internal(menu).children) { - let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker); - - // Recurse through Menus, if none of the sub-items were visible then the - // menu is hidden too. - if (visible && (item instanceof Menu)) - visible = this.setVisibility(item, addonInfo, false); - - let xulNode = this.getXULNodeForItem(item); - xulNode.hidden = !visible; - - anyVisible = anyVisible || visible; - } - - return anyVisible; - }, - - // Works out where to insert a XUL node for an item in a browser window - insertIntoXUL: function insertIntoXUL(item, node, after) { - let menupopup = null; - let before = null; - - let menu = item.parentMenu; - if (menu === this.items) { - // Insert into the overflow popup if it exists, otherwise the normal - // context menu - menupopup = this.overflowPopup; - if (!menupopup) - menupopup = this.contextMenu; - } - else { - let xulNode = this.getXULNodeForItem(menu); - menupopup = xulNode.firstChild; - } - - if (after) { - let afterNode = this.getXULNodeForItem(after); - before = afterNode.nextSibling; - } - else if (menupopup === this.contextMenu) { - let topLevel = this.topLevelItems; - if (topLevel.length > 0) - before = topLevel[topLevel.length - 1].nextSibling; - else - before = this.separator.nextSibling; - } - - menupopup.insertBefore(node, before); - }, - - // Sets the right class for XUL nodes - updateXULClass: function updateXULClass(xulNode) { - if (xulNode.parentNode == this.contextMenu) - xulNode.classList.add(TOPLEVEL_ITEM_CLASS); - else - xulNode.classList.remove(TOPLEVEL_ITEM_CLASS); - - if (xulNode.parentNode == this.overflowPopup) - xulNode.classList.add(OVERFLOW_ITEM_CLASS); - else - xulNode.classList.remove(OVERFLOW_ITEM_CLASS); - }, - - // Creates a XUL node for an item - createItem: function createItem(item, after) { - if (!this.populated) - return; - - // Create the separator if it doesn't already exist - if (!this.separator) { - let separator = this.window.document.createElement("menuseparator"); - separator.setAttribute("class", SEPARATOR_CLASS); - - // Insert before the separator created by the old context-menu if it - // exists to avoid bug 832401 - let oldSeparator = this.window.document.getElementById("jetpack-context-menu-separator"); - if (oldSeparator && oldSeparator.parentNode != this.contextMenu) - oldSeparator = null; - this.contextMenu.insertBefore(separator, oldSeparator); - } - - let type = "menuitem"; - if (item instanceof Menu) - type = "menu"; - else if (item instanceof Separator) - type = "menuseparator"; - - let xulNode = this.window.document.createElement(type); - xulNode.setAttribute("class", ITEM_CLASS); - if (item instanceof LabelledItem) { - xulNode.setAttribute("label", item.label); - if (item.accesskey) - xulNode.setAttribute("accesskey", item.accesskey); - if (item.image) { - xulNode.setAttribute("image", item.image); - if (item instanceof Menu) - xulNode.classList.add("menu-iconic"); - else - xulNode.classList.add("menuitem-iconic"); - } - if (item.data) - xulNode.setAttribute("value", item.data); - - let self = this; - xulNode.addEventListener("command", function(event) { - // Only care about clicks directly on this item - if (event.target !== xulNode) - return; - - itemActivated(item, xulNode); - }, false); - } - - this.insertIntoXUL(item, xulNode, after); - this.updateXULClass(xulNode); - xulNode.data = item.data; - - if (item instanceof Menu) { - let menupopup = this.window.document.createElement("menupopup"); - xulNode.appendChild(menupopup); - } - - this.menuMap.set(item, xulNode); - }, - - // Updates the XUL node for an item in this window - updateItem: function updateItem(item) { - if (!this.populated) - return; - - let xulNode = this.getXULNodeForItem(item); - - // TODO figure out why this requires setAttribute - xulNode.setAttribute("label", item.label); - xulNode.setAttribute("accesskey", item.accesskey || ""); - - if (item.image) { - xulNode.setAttribute("image", item.image); - if (item instanceof Menu) - xulNode.classList.add("menu-iconic"); - else - xulNode.classList.add("menuitem-iconic"); - } - else { - xulNode.removeAttribute("image"); - xulNode.classList.remove("menu-iconic"); - xulNode.classList.remove("menuitem-iconic"); - } - - if (item.data) - xulNode.setAttribute("value", item.data); - else - xulNode.removeAttribute("value"); - }, - - // Moves the XUL node for an item in this window to its new place in the - // hierarchy - moveItem: function moveItem(item, after) { - if (!this.populated) - return; - - let xulNode = this.getXULNodeForItem(item); - let oldParent = xulNode.parentNode; - - this.insertIntoXUL(item, xulNode, after); - this.updateXULClass(xulNode); - this.onXULRemoved(oldParent); - }, - - // Removes the XUL nodes for an item in every window we've ever populated. - removeItem: function removeItem(item) { - if (!this.populated) - return; - - let xulItem = this.getXULNodeForItem(item); - - let oldParent = xulItem.parentNode; - - oldParent.removeChild(xulItem); - this.menuMap.delete(item); - - this.onXULRemoved(oldParent); - }, - - // Called when any XUL nodes have been removed from a menupopup. This handles - // making sure the separator and overflow are correct - onXULRemoved: function onXULRemoved(parent) { - if (parent == this.contextMenu) { - let toplevel = this.topLevelItems; - - // If there are no more items then remove the separator - if (toplevel.length == 0) { - let separator = this.separator; - if (separator) - separator.parentNode.removeChild(separator); - } - } - else if (parent == this.overflowPopup) { - // If there are no more items then remove the overflow menu and separator - if (parent.childNodes.length == 0) { - let separator = this.separator; - separator.parentNode.removeChild(separator); - this.contextMenu.removeChild(parent.parentNode); - } - } - }, - - // Recurses through all the items owned by this module and sets their hidden - // state - updateItemVisibilities: function updateItemVisibilities(event) { - try { - if (event.type != "popupshowing") - return; - if (event.target != this.contextMenu) - return; - - if (internal(this.items).children.length == 0) - return; - - if (!this.populated) { - this.populated = true; - this.populate(this.items); - } - - let mainWindow = event.target.ownerDocument.defaultView; - this.contextMenuContentData = mainWindow.gContextMenuContentData - if (!(self.id in this.contextMenuContentData.addonInfo)) { - console.warn("No context menu state data was provided."); - return; - } - let addonInfo = this.contextMenuContentData.addonInfo[self.id]; - lastContextProcessId = addonInfo.processID; - this.setVisibility(this.items, addonInfo.items, true); - } - catch (e) { - console.exception(e); - } - }, - - // Counts the number of visible items across all modules and makes sure they - // are in the right place between the top level context menu and the overflow - // menu - updateOverflowState: function updateOverflowState(event) { - try { - if (event.type != "popupshowing") - return; - if (event.target != this.contextMenu) - return; - - // The main items will be in either the top level context menu or the - // overflow menu at this point. Count the visible ones and if they are in - // the wrong place move them - let toplevel = this.topLevelItems; - let overflow = this.overflowItems; - let visibleCount = countVisibleItems(toplevel) + - countVisibleItems(overflow); - - if (visibleCount == 0) { - let separator = this.separator; - if (separator) - separator.hidden = true; - let overflowMenu = this.overflowMenu; - if (overflowMenu) - overflowMenu.hidden = true; - } - else if (visibleCount > MenuManager.overflowThreshold) { - this.separator.hidden = false; - let overflowPopup = this.overflowPopup; - if (overflowPopup) - overflowPopup.parentNode.hidden = false; - - if (toplevel.length > 0) { - // The overflow menu shouldn't exist here but let's play it safe - if (!overflowPopup) { - let overflowMenu = this.window.document.createElement("menu"); - overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS); - overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL); - overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY); - this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling); - - overflowPopup = this.window.document.createElement("menupopup"); - overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS); - overflowMenu.appendChild(overflowPopup); - } - - for (let xulNode of toplevel) { - overflowPopup.appendChild(xulNode); - this.updateXULClass(xulNode); - } - } - } - else { - this.separator.hidden = false; - - if (overflow.length > 0) { - // Move all the overflow nodes out of the overflow menu and position - // them immediately before it - for (let xulNode of overflow) { - this.contextMenu.insertBefore(xulNode, xulNode.parentNode.parentNode); - this.updateXULClass(xulNode); - } - this.contextMenu.removeChild(this.overflowMenu); - } - } - } - catch (e) { - console.exception(e); - } - } -}); - -// This wraps every window that we've seen -var WindowWrapper = Class({ - initialize: function initialize(window) { - this.window = window; - this.menus = [ - new MenuWrapper(this, contentContextMenu, window.document.getElementById("contentAreaContextMenu")), - ]; - }, - - destroy: function destroy() { - for (let menuWrapper of this.menus) - menuWrapper.destroy(); - }, - - getMenuWrapperForItem: function getMenuWrapperForItem(item) { - let root = item.parentMenu; - while (root.parentMenu) - root = root.parentMenu; - - for (let wrapper of this.menus) { - if (wrapper.items === root) - return wrapper; - } - - return null; - } -}); - -var MenuManager = { - windowMap: new Map(), - - get overflowThreshold() { - let prefs = require("./preferences/service"); - return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT); - }, - - // When a new window is added start watching it for context menu shows - onTrack: function onTrack(window) { - if (!isBrowser(window)) - return; - - // Generally shouldn't happen, but just in case - if (this.windowMap.has(window)) { - console.warn("Already seen this window"); - return; - } - - let winWrapper = WindowWrapper(window); - this.windowMap.set(window, winWrapper); - }, - - onUntrack: function onUntrack(window) { - if (!isBrowser(window)) - return; - - let winWrapper = this.windowMap.get(window); - // This shouldn't happen but protect against it anyway - if (!winWrapper) - return; - winWrapper.destroy(); - - this.windowMap.delete(window); - }, - - // Creates a XUL node for an item in every window we've already populated - createItem: function createItem(item, after) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.createItem(item, after); - } - }, - - // Updates the XUL node for an item in every window we've already populated - updateItem: function updateItem(item) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.updateItem(item); - } - }, - - // Moves the XUL node for an item in every window we've ever populated to its - // new place in the hierarchy - moveItem: function moveItem(item, after) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.moveItem(item, after); - } - }, - - // Removes the XUL nodes for an item in every window we've ever populated. - removeItem: function removeItem(item) { - for (let [window, winWrapper] of this.windowMap) { - let menuWrapper = winWrapper.getMenuWrapperForItem(item); - if (menuWrapper) - menuWrapper.removeItem(item); - } - } -}; - -WindowTracker(MenuManager); diff --git a/addon-sdk/source/lib/sdk/context-menu/context.js b/addon-sdk/source/lib/sdk/context-menu/context.js deleted file mode 100644 index fc5aea500..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/context.js +++ /dev/null @@ -1,147 +0,0 @@ -/* 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/. */ - -const { Class } = require("../core/heritage"); -const { extend } = require("../util/object"); -const { MatchPattern } = require("../util/match-pattern"); -const readers = require("./readers"); - -// Context class is required to implement a single `isCurrent(target)` method -// that must return boolean value indicating weather given target matches a -// context or not. Most context implementations below will have an associated -// reader that way context implementation can setup a reader to extract necessary -// information to make decision if target is matching a context. -const Context = Class({ - isRequired: false, - isCurrent(target) { - throw Error("Context class must implement isCurrent(target) method"); - }, - get required() { - Object.defineProperty(this, "required", { - value: Object.assign(Object.create(Object.getPrototypeOf(this)), - this, - {isRequired: true}) - }); - return this.required; - } -}); -Context.required = function(...params) { - return Object.assign(new this(...params), {isRequired: true}); -}; -exports.Context = Context; - - -// Next few context implementations use an associated reader to extract info -// from the context target and story it to a private symbol associtaed with -// a context implementation. That way name collisions are avoided while required -// information is still carried along. -const isPage = Symbol("context/page?") -const PageContext = Class({ - extends: Context, - read: {[isPage]: new readers.isPage()}, - isCurrent: target => target[isPage] -}); -exports.Page = PageContext; - -const isFrame = Symbol("context/frame?"); -const FrameContext = Class({ - extends: Context, - read: {[isFrame]: new readers.isFrame()}, - isCurrent: target => target[isFrame] -}); -exports.Frame = FrameContext; - -const selection = Symbol("context/selection") -const SelectionContext = Class({ - read: {[selection]: new readers.Selection()}, - isCurrent: target => !!target[selection] -}); -exports.Selection = SelectionContext; - -const link = Symbol("context/link"); -const LinkContext = Class({ - extends: Context, - read: {[link]: new readers.LinkURL()}, - isCurrent: target => !!target[link] -}); -exports.Link = LinkContext; - -const isEditable = Symbol("context/editable?") -const EditableContext = Class({ - extends: Context, - read: {[isEditable]: new readers.isEditable()}, - isCurrent: target => target[isEditable] -}); -exports.Editable = EditableContext; - - -const mediaType = Symbol("context/mediaType") - -const ImageContext = Class({ - extends: Context, - read: {[mediaType]: new readers.MediaType()}, - isCurrent: target => target[mediaType] === "image" -}); -exports.Image = ImageContext; - - -const VideoContext = Class({ - extends: Context, - read: {[mediaType]: new readers.MediaType()}, - isCurrent: target => target[mediaType] === "video" -}); -exports.Video = VideoContext; - - -const AudioContext = Class({ - extends: Context, - read: {[mediaType]: new readers.MediaType()}, - isCurrent: target => target[mediaType] === "audio" -}); -exports.Audio = AudioContext; - -const isSelectorMatch = Symbol("context/selector/mathches?") -const SelectorContext = Class({ - extends: Context, - initialize(selector) { - this.selector = selector; - // Each instance of selector context will need to store read - // data into different field, so that case with multilpe selector - // contexts won't cause a conflicts. - this[isSelectorMatch] = Symbol(selector); - this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)}; - }, - isCurrent(target) { - return target[this[isSelectorMatch]]; - } -}); -exports.Selector = SelectorContext; - -const url = Symbol("context/url"); -const URLContext = Class({ - extends: Context, - initialize(pattern) { - this.pattern = new MatchPattern(pattern); - }, - read: {[url]: new readers.PageURL()}, - isCurrent(target) { - return this.pattern.test(target[url]); - } -}); -exports.URL = URLContext; - -var PredicateContext = Class({ - extends: Context, - initialize(isMatch) { - if (typeof(isMatch) !== "function") { - throw TypeError("Predicate context mus be passed a function"); - } - - this.isMatch = isMatch - }, - isCurrent(target) { - return this.isMatch(target); - } -}); -exports.Predicate = PredicateContext; diff --git a/addon-sdk/source/lib/sdk/context-menu/core.js b/addon-sdk/source/lib/sdk/context-menu/core.js deleted file mode 100644 index c64cddfe8..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/core.js +++ /dev/null @@ -1,384 +0,0 @@ -/* 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 Contexts = require("./context"); -const Readers = require("./readers"); -const Component = require("../ui/component"); -const { Class } = require("../core/heritage"); -const { map, filter, object, reduce, keys, symbols, - pairs, values, each, some, isEvery, count } = require("../util/sequence"); -const { loadModule } = require("framescript/manager"); -const { Cu, Cc, Ci } = require("chrome"); -const prefs = require("sdk/preferences/service"); - -const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"] - .getService(Ci.nsIMessageListenerManager); -const preferencesService = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - getBranch(null); - - -const readTable = Symbol("context-menu/read-table"); -const nameTable = Symbol("context-menu/name-table"); -const onContext = Symbol("context-menu/on-context"); -const isMatching = Symbol("context-menu/matching-handler?"); - -exports.onContext = onContext; -exports.readTable = readTable; -exports.nameTable = nameTable; - - -const propagateOnContext = (item, data) => - each(child => child[onContext](data), item.state.children); - -const isContextMatch = item => !item[isMatching] || item[isMatching](); - -// For whatever reason addWeakMessageListener does not seems to work as our -// instance seems to dropped even though it's alive. This is simple workaround -// to avoid dead object excetptions. -const WeakMessageListener = function(receiver, handler="receiveMessage") { - this.receiver = receiver - this.handler = handler -}; -WeakMessageListener.prototype = { - constructor: WeakMessageListener, - receiveMessage(message) { - if (Cu.isDeadWrapper(this.receiver)) { - message.target.messageManager.removeMessageListener(message.name, this); - } - else { - this.receiver[this.handler](message); - } - } -}; - -const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold"; -const onMessage = Symbol("context-menu/message-listener"); -const onPreferceChange = Symbol("context-menu/preference-change"); -const ContextMenuExtension = Class({ - extends: Component, - initialize: Component, - setup() { - const messageListener = new WeakMessageListener(this, onMessage); - loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame"); - globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener); - globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener); - - preferencesService.addObserver(OVERFLOW_THRESH, this, false); - }, - observe(_, __, name) { - if (name === OVERFLOW_THRESH) { - const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10); - this[Component.patch]({overflowThreshold}); - } - }, - [onMessage]({name, data, target}) { - if (name === "sdk/context-menu/read") - this[onContext]({target, data}); - if (name === "sdk/context-menu/readers?") - target.messageManager.sendAsyncMessage("sdk/context-menu/readers", - JSON.parse(JSON.stringify(this.state.readers))); - }, - [Component.initial](options={}, children) { - const element = options.element || null; - const target = options.target || null; - const readers = Object.create(null); - const users = Object.create(null); - const registry = new WeakSet(); - const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10); - - return { target, children: [], readers, users, element, - registry, overflowThreshold }; - }, - [Component.isUpdated](before, after) { - // Update only if target changed, since there is no point in re-rendering - // when children are. Also new items added won't be in sync with a latest - // context target so we should really just render before drawing context - // menu. - return before.target !== after.target; - }, - [Component.render]({element, children, overflowThreshold}) { - if (!element) return null; - - const items = children.filter(isContextMatch); - const body = items.length === 0 ? items : - items.length < overflowThreshold ? [new Separator(), - ...items] : - [{tagName: "menu", - className: "sdk-context-menu-overflow-menu", - label: "Add-ons", - accesskey: "A", - children: [{tagName: "menupopup", - children: items}]}]; - return { - element: element, - tagName: "menugroup", - style: "-moz-box-orient: vertical;", - className: "sdk-context-menu-extension", - children: body - } - }, - // Adds / remove child to it's own list. - add(item) { - this[Component.patch]({children: this.state.children.concat(item)}); - }, - remove(item) { - this[Component.patch]({ - children: this.state.children.filter(x => x !== item) - }); - }, - register(item) { - const { users, registry } = this.state; - if (registry.has(item)) return; - registry.add(item); - - // Each (ContextHandler) item has a readTable that is a - // map of keys to readers extracting them from the content. - // During the registraction we update intrnal record of unique - // readers and users per reader. Most context will have a reader - // shared across all instances there for map of users per reader - // is stored separately from the reader so that removing reader - // will occur only when no users remain. - const table = item[readTable]; - // Context readers store data in private symbols so we need to - // collect both table keys and private symbols. - const names = [...keys(table), ...symbols(table)]; - const readers = map(name => table[name], names); - // Create delta for registered readers that will be merged into - // internal readers table. - const added = filter(x => !users[x.id], readers); - const delta = object(...map(x => [x.id, x], added)); - - const update = reduce((update, reader) => { - const n = update[reader.id] || 0; - update[reader.id] = n + 1; - return update; - }, Object.assign({}, users), readers); - - // Patch current state with a changes that registered item caused. - this[Component.patch]({users: update, - readers: Object.assign(this.state.readers, delta)}); - - if (count(added)) { - globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers", - JSON.parse(JSON.stringify(delta))); - } - }, - unregister(item) { - const { users, registry } = this.state; - if (!registry.has(item)) return; - registry.delete(item); - - const table = item[readTable]; - const names = [...keys(table), ...symbols(table)]; - const readers = map(name => table[name], names); - const update = reduce((update, reader) => { - update[reader.id] = update[reader.id] - 1; - return update; - }, Object.assign({}, users), readers); - const removed = filter(id => !update[id], keys(update)); - const delta = object(...map(x => [x, null], removed)); - - this[Component.patch]({users: update, - readers: Object.assign(this.state.readers, delta)}); - - if (count(removed)) { - globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers", - JSON.parse(JSON.stringify(delta))); - } - }, - - [onContext]({data, target}) { - propagateOnContext(this, data); - const document = target.ownerDocument; - const element = document.getElementById("contentAreaContextMenu"); - - this[Component.patch]({target: data, element: element}); - } -});this, -exports.ContextMenuExtension = ContextMenuExtension; - -// Takes an item options and -const makeReadTable = ({context, read}) => { - // Result of this function is a tuple of all readers & - // name, reader id pairs. - - // Filter down to contexts that have a reader associated. - const contexts = filter(context => context.read, context); - // Merge all contexts read maps to a single hash, note that there should be - // no name collisions as context implementations expect to use private - // symbols for storing it's read data. - return Object.assign({}, ...map(({read}) => read, contexts), read); -} - -const readTarget = (nameTable, data) => - object(...map(([name, id]) => [name, data[id]], nameTable)) - -const ContextHandler = Class({ - extends: Component, - initialize: Component, - get context() { - return this.state.options.context; - }, - get read() { - return this.state.options.read; - }, - [Component.initial](options) { - return { - table: makeReadTable(options), - requiredContext: filter(context => context.isRequired, options.context), - optionalContext: filter(context => !context.isRequired, options.context) - } - }, - [isMatching]() { - const {target, requiredContext, optionalContext} = this.state; - return isEvery(context => context.isCurrent(target), requiredContext) && - (count(optionalContext) === 0 || - some(context => context.isCurrent(target), optionalContext)); - }, - setup() { - const table = makeReadTable(this.state.options); - this[readTable] = table; - this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)), - ...map(name => [name, table[name].id], keys(table))]; - - - contextMenu.register(this); - - each(child => contextMenu.remove(child), this.state.children); - contextMenu.add(this); - }, - dispose() { - contextMenu.remove(this); - - each(child => contextMenu.unregister(child), this.state.children); - contextMenu.unregister(this); - }, - // Internal `Symbol("onContext")` method is invoked when "contextmenu" event - // occurs in content process. Context handles with children delegate to each - // child and patch it's internal state to reflect new contextmenu target. - [onContext](data) { - propagateOnContext(this, data); - this[Component.patch]({target: readTarget(this[nameTable], data)}); - } -}); -const isContextHandler = item => item instanceof ContextHandler; - -exports.ContextHandler = ContextHandler; - -const Menu = Class({ - extends: ContextHandler, - [isMatching]() { - return ContextHandler.prototype[isMatching].call(this) && - this.state.children.filter(isContextHandler) - .some(isContextMatch); - }, - [Component.render]({children, options}) { - const items = children.filter(isContextMatch); - return {tagName: "menu", - className: "sdk-context-menu menu-iconic", - label: options.label, - accesskey: options.accesskey, - image: options.icon, - children: [{tagName: "menupopup", - children: items}]}; - } -}); -exports.Menu = Menu; - -const onCommand = Symbol("context-menu/item/onCommand"); -const Item = Class({ - extends: ContextHandler, - get onClick() { - return this.state.options.onClick; - }, - [Component.render]({options}) { - const {label, icon, accesskey} = options; - return {tagName: "menuitem", - className: "sdk-context-menu-item menuitem-iconic", - label, - accesskey, - image: icon, - oncommand: this}; - }, - handleEvent(event) { - if (this.onClick) - this.onClick(this.state.target); - } -}); -exports.Item = Item; - -var Separator = Class({ - extends: Component, - initialize: Component, - [Component.render]() { - return {tagName: "menuseparator", - className: "sdk-context-menu-separator"} - }, - [onContext]() { - - } -}); -exports.Separator = Separator; - -exports.Contexts = Contexts; -exports.Readers = Readers; - -const createElement = (vnode, {document}) => { - const node = vnode.namespace ? - document.createElementNS(vnode.namespace, vnode.tagName) : - document.createElement(vnode.tagName); - - node.setAttribute("data-component-path", vnode[Component.path]); - - each(([key, value]) => { - if (key === "tagName") { - return; - } - if (key === "children") { - return; - } - - if (key.startsWith("on")) { - node.addEventListener(key.substr(2), value) - return; - } - - if (typeof(value) !== "object" && - typeof(value) !== "function" && - value !== void(0) && - value !== null) - { - if (key === "className") { - node[key] = value; - } - else { - node.setAttribute(key, value); - } - return; - } - }, pairs(vnode)); - - each(child => node.appendChild(createElement(child, {document})), vnode.children); - return node; -}; - -const htmlWriter = tree => { - if (tree !== null) { - const root = tree.element; - const node = createElement(tree, {document: root.ownerDocument}); - const before = root.querySelector("[data-component-path='/']"); - if (before) { - root.replaceChild(node, before); - } else { - root.appendChild(node); - } - } -}; - - -const contextMenu = ContextMenuExtension(); -exports.contextMenu = contextMenu; -Component.mount(contextMenu, htmlWriter); diff --git a/addon-sdk/source/lib/sdk/context-menu/readers.js b/addon-sdk/source/lib/sdk/context-menu/readers.js deleted file mode 100644 index 5078f8f29..000000000 --- a/addon-sdk/source/lib/sdk/context-menu/readers.js +++ /dev/null @@ -1,112 +0,0 @@ -/* 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/. */ -const { Class } = require("../core/heritage"); -const { extend } = require("../util/object"); -const { memoize, method, identity } = require("../lang/functional"); - -const serializeCategory = ({type}) => ({ category: `reader/${type}()` }); - -const Reader = Class({ - initialize() { - this.id = `reader/${this.type}()` - }, - toJSON() { - return serializeCategory(this); - } -}); - - -const MediaTypeReader = Class({ extends: Reader, type: "MediaType" }); -exports.MediaType = MediaTypeReader; - -const LinkURLReader = Class({ extends: Reader, type: "LinkURL" }); -exports.LinkURL = LinkURLReader; - -const SelectionReader = Class({ extends: Reader, type: "Selection" }); -exports.Selection = SelectionReader; - -const isPageReader = Class({ extends: Reader, type: "isPage" }); -exports.isPage = isPageReader; - -const isFrameReader = Class({ extends: Reader, type: "isFrame" }); -exports.isFrame = isFrameReader; - -const isEditable = Class({ extends: Reader, type: "isEditable"}); -exports.isEditable = isEditable; - - - -const ParameterizedReader = Class({ - extends: Reader, - readParameter: function(value) { - return value; - }, - toJSON: function() { - var json = serializeCategory(this); - json[this.parameter] = this[this.parameter]; - return json; - }, - initialize(...params) { - if (params.length) { - this[this.parameter] = this.readParameter(...params); - } - this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`; - } -}); -exports.ParameterizedReader = ParameterizedReader; - - -const QueryReader = Class({ - extends: ParameterizedReader, - type: "Query", - parameter: "path" -}); -exports.Query = QueryReader; - - -const AttributeReader = Class({ - extends: ParameterizedReader, - type: "Attribute", - parameter: "name" -}); -exports.Attribute = AttributeReader; - -const SrcURLReader = Class({ - extends: AttributeReader, - name: "src", -}); -exports.SrcURL = SrcURLReader; - -const PageURLReader = Class({ - extends: QueryReader, - path: "ownerDocument.URL", -}); -exports.PageURL = PageURLReader; - -const SelectorMatchReader = Class({ - extends: ParameterizedReader, - type: "SelectorMatch", - parameter: "selector" -}); -exports.SelectorMatch = SelectorMatchReader; - -const extractors = new WeakMap(); -extractors.id = 0; - - -var Extractor = Class({ - extends: ParameterizedReader, - type: "Extractor", - parameter: "source", - initialize: function(f) { - this[this.parameter] = String(f); - if (!extractors.has(f)) { - extractors.id = extractors.id + 1; - extractors.set(f, extractors.id); - } - - this.id = `reader/${this.type}.for(${extractors.get(f)})` - } -}); -exports.Extractor = Extractor; diff --git a/addon-sdk/source/lib/sdk/context-menu@2.js b/addon-sdk/source/lib/sdk/context-menu@2.js deleted file mode 100644 index 45ad804e9..000000000 --- a/addon-sdk/source/lib/sdk/context-menu@2.js +++ /dev/null @@ -1,32 +0,0 @@ -/* 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 shared = require("toolkit/require"); -const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core"); -const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable") -const { Class } = require("sdk/core/heritage") - -const makeDisposable = Type => Class({ - extends: Type, - implements: [Disposable], - initialize: Type.prototype.initialize, - setup(...params) { - Type.prototype.setup.call(this, ...params); - setupDisposable(this); - }, - dispose(...params) { - disposeDisposable(this); - Type.prototype.dispose.call(this, ...params); - } -}); - -exports.Separator = Separator; -exports.Contexts = Contexts; -exports.Readers = Readers; - -// Subclass Item & Menu shared classes so their items -// will be unloaded when add-on is unloaded. -exports.Item = makeDisposable(Item); -exports.Menu = makeDisposable(Menu); diff --git a/addon-sdk/source/lib/sdk/core/disposable.js b/addon-sdk/source/lib/sdk/core/disposable.js deleted file mode 100644 index 19f7eaa9f..000000000 --- a/addon-sdk/source/lib/sdk/core/disposable.js +++ /dev/null @@ -1,186 +0,0 @@ -/* 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": "experimental" -}; - -const { Class } = require("./heritage"); -const { Observer, subscribe, unsubscribe, observe } = require("./observer"); -const { isWeak } = require("./reference"); -const SDKWeakSet = require("../lang/weak-set"); - -const method = require("../../method/core"); - -const unloadSubject = require('@loader/unload'); -const addonUnloadTopic = "sdk:loader:destroy"; - -const uninstall = method("disposable/uninstall"); -exports.uninstall = uninstall; - -const shutdown = method("disposable/shutdown"); -exports.shutdown = shutdown; - -const disable = method("disposable/disable"); -exports.disable = disable; - -const upgrade = method("disposable/upgrade"); -exports.upgrade = upgrade; - -const downgrade = method("disposable/downgrade"); -exports.downgrade = downgrade; - -const unload = method("disposable/unload"); -exports.unload = unload; - -const dispose = method("disposable/dispose"); -exports.dispose = dispose; -dispose.define(Object, object => object.dispose()); - -const setup = method("disposable/setup"); -exports.setup = setup; -setup.define(Object, (object, ...args) => object.setup(...args)); - -// DisposablesUnloadObserver is the class which subscribe the -// Observer Service to be notified when the add-on loader is -// unloading to be able to dispose all the existent disposables. -const DisposablesUnloadObserver = Class({ - implements: [Observer], - initialize: function(...args) { - // Set of the non-weak disposables registered to be disposed. - this.disposables = new Set(); - // Target of the weak disposables registered to be disposed - // (and tracked on this target using the SDK weak-set module). - this.weakDisposables = {}; - }, - subscribe(disposable) { - if (isWeak(disposable)) { - SDKWeakSet.add(this.weakDisposables, disposable); - } else { - this.disposables.add(disposable); - } - }, - unsubscribe(disposable) { - if (isWeak(disposable)) { - SDKWeakSet.remove(this.weakDisposables, disposable); - } else { - this.disposables.delete(disposable); - } - }, - tryUnloadDisposable(disposable) { - try { - if (disposable) { - unload(disposable); - } - } catch(e) { - console.error("Error unloading a", - isWeak(disposable) ? "weak disposable" : "disposable", - disposable, e); - } - }, - unloadAll() { - // Remove all the subscribed disposables. - for (let disposable of this.disposables) { - this.tryUnloadDisposable(disposable); - } - - this.disposables.clear(); - - // Remove all the subscribed weak disposables. - for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) { - this.tryUnloadDisposable(disposable); - } - - SDKWeakSet.clear(this.weakDisposables); - } -}); -const disposablesUnloadObserver = new DisposablesUnloadObserver(); - -// The DisposablesUnloadObserver instance is the only object which subscribes -// the Observer Service directly, it observes add-on unload notifications in -// order to trigger `unload` on all its subscribed disposables. -observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => { - const isUnloadTopic = topic === addonUnloadTopic; - const isUnloadSubject = subject.wrappedJSObject === unloadSubject; - if (isUnloadTopic && isUnloadSubject) { - unsubscribe(disposablesUnloadObserver, addonUnloadTopic); - disposablesUnloadObserver.unloadAll(); - } -}); - -subscribe(disposablesUnloadObserver, addonUnloadTopic, false); - -// Set's up disposable instance. -const setupDisposable = disposable => { - disposablesUnloadObserver.subscribe(disposable); -}; -exports.setupDisposable = setupDisposable; - -// Tears down disposable instance. -const disposeDisposable = disposable => { - disposablesUnloadObserver.unsubscribe(disposable); -}; -exports.disposeDisposable = disposeDisposable; - -// Base type that takes care of disposing it's instances on add-on unload. -// Also makes sure to remove unload listener if it's already being disposed. -const Disposable = Class({ - initialize: function(...args) { - // First setup instance before initializing it's disposal. If instance - // fails to initialize then there is no instance to be disposed at the - // unload. - setup(this, ...args); - setupDisposable(this); - }, - destroy: function(reason) { - // Destroying disposable removes unload handler so that attempt to dispose - // won't be made at unload & delegates to dispose. - disposeDisposable(this); - unload(this, reason); - }, - setup: function() { - // Implement your initialize logic here. - }, - dispose: function() { - // Implement your cleanup logic here. - } -}); -exports.Disposable = Disposable; - -const unloaders = { - destroy: dispose, - uninstall: uninstall, - shutdown: shutdown, - disable: disable, - upgrade: upgrade, - downgrade: downgrade -}; - -const unloaded = new WeakMap(); -unload.define(Disposable, (disposable, reason) => { - if (!unloaded.get(disposable)) { - unloaded.set(disposable, true); - // Pick an unload handler associated with an unload - // reason (falling back to destroy if not found) and - // delegate unloading to it. - const unload = unloaders[reason] || unloaders.destroy; - unload(disposable); - } -}); - -// If add-on is disabled manually, it's being upgraded, downgraded -// or uninstalled `dispose` is invoked to undo any changes that -// has being done by it in this session. -disable.define(Disposable, dispose); -downgrade.define(Disposable, dispose); -upgrade.define(Disposable, dispose); -uninstall.define(Disposable, dispose); - -// If application is shut down no dispose is invoked as undo-ing -// changes made by instance is likely to just waste of resources & -// increase shutdown time. Although specefic components may choose -// to implement shutdown handler that does something better. -shutdown.define(Disposable, disposable => {}); diff --git a/addon-sdk/source/lib/sdk/core/heritage.js b/addon-sdk/source/lib/sdk/core/heritage.js deleted file mode 100644 index fc87ba1f5..000000000 --- a/addon-sdk/source/lib/sdk/core/heritage.js +++ /dev/null @@ -1,184 +0,0 @@ -/* 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" -}; - -var getPrototypeOf = Object.getPrototypeOf; -var getNames = x => [...Object.getOwnPropertyNames(x), - ...Object.getOwnPropertySymbols(x)]; -var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; -var create = Object.create; -var freeze = Object.freeze; -var unbind = Function.call.bind(Function.bind, Function.call); - -// This shortcut makes sure that we do perform desired operations, even if -// associated methods have being overridden on the used object. -var owns = unbind(Object.prototype.hasOwnProperty); -var apply = unbind(Function.prototype.apply); -var slice = Array.slice || unbind(Array.prototype.slice); -var reduce = Array.reduce || unbind(Array.prototype.reduce); -var map = Array.map || unbind(Array.prototype.map); -var concat = Array.concat || unbind(Array.prototype.concat); - -// Utility function to get own properties descriptor map. -function getOwnPropertyDescriptors(object) { - return reduce(getNames(object), function(descriptor, name) { - descriptor[name] = getOwnPropertyDescriptor(object, name); - return descriptor; - }, {}); -} - -function isDataProperty(property) { - var value = property.value; - var type = typeof(property.value); - return "value" in property && - (type !== "object" || value === null) && - type !== "function"; -} - -function getDataProperties(object) { - var properties = getOwnPropertyDescriptors(object); - return getNames(properties).reduce(function(result, name) { - var property = properties[name]; - if (isDataProperty(property)) { - result[name] = { - value: property.value, - writable: true, - configurable: true, - enumerable: false - }; - } - return result; - }, {}) -} - -/** - * Takes `source` object as an argument and returns identical object - * with the difference that all own properties will be non-enumerable - */ -function obscure(source) { - var descriptor = reduce(getNames(source), function(descriptor, name) { - var property = getOwnPropertyDescriptor(source, name); - property.enumerable = false; - descriptor[name] = property; - return descriptor; - }, {}); - return create(getPrototypeOf(source), descriptor); -} -exports.obscure = obscure; - -/** - * Takes arbitrary number of source objects and returns fresh one, that - * inherits from the same prototype as a first argument and implements all - * own properties of all argument objects. If two or more argument objects - * have own properties with the same name, the property is overridden, with - * precedence from right to left, implying, that properties of the object on - * the left are overridden by a same named property of the object on the right. - */ -var mix = function(source) { - var descriptor = reduce(slice(arguments), function(descriptor, source) { - return reduce(getNames(source), function(descriptor, name) { - descriptor[name] = getOwnPropertyDescriptor(source, name); - return descriptor; - }, descriptor); - }, {}); - - return create(getPrototypeOf(source), descriptor); -}; -exports.mix = mix; - -/** - * Returns a frozen object with that inherits from the given `prototype` and - * implements all own properties of the given `properties` object. - */ -function extend(prototype, properties) { - return create(prototype, getOwnPropertyDescriptors(properties)); -} -exports.extend = extend; - -/** - * Returns a constructor function with a proper `prototype` setup. Returned - * constructor's `prototype` inherits from a given `options.extends` or - * `Class.prototype` if omitted and implements all the properties of the - * given `option`. If `options.implemens` array is passed, it's elements - * will be mixed into prototype as well. Also, `options.extends` can be - * a function or a prototype. If function than it's prototype is used as - * an ancestor of the prototype, if it's an object that it's used directly. - * Also `options.implements` may contain functions or objects, in case of - * functions their prototypes are used for mixing. - */ -var Class = new function() { - function prototypeOf(input) { - return typeof(input) === 'function' ? input.prototype : input; - } - var none = freeze([]); - - return function Class(options) { - // Create descriptor with normalized `options.extends` and - // `options.implements`. - var descriptor = { - // Normalize extends property of `options.extends` to a prototype object - // in case it's constructor. If property is missing that fallback to - // `Type.prototype`. - extends: owns(options, 'extends') ? - prototypeOf(options.extends) : Class.prototype, - // Normalize `options.implements` to make sure that it's array of - // prototype objects instead of constructor functions. - implements: owns(options, 'implements') ? - freeze(map(options.implements, prototypeOf)) : none - }; - - // Create array of property descriptors who's properties will be defined - // on the resulting prototype. Note: Using reflection `concat` instead of - // method as it may be overridden. - var descriptors = concat(descriptor.implements, options, descriptor, { - constructor: constructor - }); - - // Note: we use reflection `apply` in the constructor instead of method - // call since later may be overridden. - function constructor() { - var instance = create(prototype, attributes); - if (initialize) apply(initialize, instance, arguments); - return instance; - } - // Create `prototype` that inherits from given ancestor passed as - // `options.extends`, falling back to `Type.prototype`, implementing all - // properties of given `options.implements` and `options` itself. - var prototype = extend(descriptor.extends, mix.apply(mix, descriptors)); - var initialize = prototype.initialize; - - // Combine ancestor attributes with prototype's attributes so that - // ancestors attributes also become initializeable. - var attributes = mix(descriptor.extends.constructor.attributes || {}, - getDataProperties(prototype)); - - constructor.attributes = attributes; - Object.defineProperty(constructor, 'prototype', { - configurable: false, - writable: false, - value: prototype - }); - return constructor; - }; -} -Class.prototype = extend(null, obscure({ - constructor: function constructor() { - this.initialize.apply(this, arguments); - return this; - }, - initialize: function initialize() { - // Do your initialization logic here - }, - // Copy useful properties from `Object.prototype`. - toString: Object.prototype.toString, - toLocaleString: Object.prototype.toLocaleString, - toSource: Object.prototype.toSource, - valueOf: Object.prototype.valueOf, - isPrototypeOf: Object.prototype.isPrototypeOf -})); -exports.Class = freeze(Class); diff --git a/addon-sdk/source/lib/sdk/core/namespace.js b/addon-sdk/source/lib/sdk/core/namespace.js deleted file mode 100644 index 3ceb73b72..000000000 --- a/addon-sdk/source/lib/sdk/core/namespace.js +++ /dev/null @@ -1,43 +0,0 @@ -/* 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 create = Object.create; -const prototypeOf = Object.getPrototypeOf; - -/** - * Returns a new namespace, function that may can be used to access an - * namespaced object of the argument argument. Namespaced object are associated - * with owner objects via weak references. Namespaced objects inherit from the - * owners ancestor namespaced object. If owner's ancestor is `null` then - * namespaced object inherits from given `prototype`. Namespaces can be used - * to define internal APIs that can be shared via enclosing `namespace` - * function. - * @examples - * const internals = ns(); - * internals(object).secret = secret; - */ -function ns() { - const map = new WeakMap(); - return function namespace(target) { - if (!target) // If `target` is not an object return `target` itself. - return target; - // If target has no namespaced object yet, create one that inherits from - // the target prototype's namespaced object. - if (!map.has(target)) - map.set(target, create(namespace(prototypeOf(target) || null))); - - return map.get(target); - }; -}; - -// `Namespace` is a e4x function in the scope, so we export the function also as -// `ns` as alias to avoid clashing. -exports.ns = ns; -exports.Namespace = ns; diff --git a/addon-sdk/source/lib/sdk/core/observer.js b/addon-sdk/source/lib/sdk/core/observer.js deleted file mode 100644 index 7e11bf8f9..000000000 --- a/addon-sdk/source/lib/sdk/core/observer.js +++ /dev/null @@ -1,89 +0,0 @@ -/* 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": "experimental" -}; - - -const { Cc, Ci, Cr, Cu } = require("chrome"); -const { Class } = require("./heritage"); -const { isWeak } = require("./reference"); -const method = require("../../method/core"); - -const observerService = Cc['@mozilla.org/observer-service;1']. - getService(Ci.nsIObserverService); - -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - -// This is a method that will be invoked when notification observer -// subscribed to occurs. -const observe = method("observer/observe"); -exports.observe = observe; - -// Method to subscribe to the observer notification. -const subscribe = method("observe/subscribe"); -exports.subscribe = subscribe; - - -// Method to unsubscribe from the observer notifications. -const unsubscribe = method("observer/unsubscribe"); -exports.unsubscribe = unsubscribe; - - -// This is wrapper class that takes a `delegate` and produces -// instance of `nsIObserver` which will delegate to a given -// object when observer notification occurs. -const ObserverDelegee = Class({ - initialize: function(delegate) { - this.delegate = delegate; - }, - QueryInterface: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - - return this; - }, - observe: function(subject, topic, data) { - observe(this.delegate, subject, topic, data); - } -}); - - -// Class that can be either mixed in or inherited from in -// order to subscribe / unsubscribe for observer notifications. -const Observer = Class({}); -exports.Observer = Observer; - -// Weak maps that associates instance of `ObserverDelegee` with -// an actual observer. It ensures that `ObserverDelegee` instance -// won't be GC-ed until given `observer` is. -const subscribers = new WeakMap(); - -// Implementation of `subscribe` for `Observer` type just registers -// observer for an observer service. If `isWeak(observer)` is `true` -// observer service won't hold strong reference to a given `observer`. -subscribe.define(Observer, (observer, topic) => { - if (!subscribers.has(observer)) { - const delegee = new ObserverDelegee(observer); - subscribers.set(observer, delegee); - addObserver(delegee, topic, isWeak(observer)); - } -}); - -// Unsubscribes `observer` from observer notifications for the -// given `topic`. -unsubscribe.define(Observer, (observer, topic) => { - const delegee = subscribers.get(observer); - if (delegee) { - subscribers.delete(observer); - removeObserver(delegee, topic); - } -}); diff --git a/addon-sdk/source/lib/sdk/core/promise.js b/addon-sdk/source/lib/sdk/core/promise.js deleted file mode 100644 index f4bd7b0f5..000000000 --- a/addon-sdk/source/lib/sdk/core/promise.js +++ /dev/null @@ -1,118 +0,0 @@ -/* 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'; - -/* - * Uses `Promise.jsm` as a core implementation, with additional sugar - * from previous implementation, with inspiration from `Q` and `when` - * - * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm - * https://github.com/cujojs/when - * https://github.com/kriskowal/q - */ -const PROMISE_URI = 'resource://gre/modules/Promise.jsm'; - -getEnvironment.call(this, function ({ require, exports, module, Cu }) { - -const Promise = Cu.import(PROMISE_URI, {}).Promise; -const { Debugging, defer, resolve, all, reject, race } = Promise; - -module.metadata = { - 'stability': 'unstable' -}; - -var promised = (function() { - // Note: Define shortcuts and utility functions here in order to avoid - // slower property accesses and unnecessary closure creations on each - // call of this popular function. - - var call = Function.call; - var concat = Array.prototype.concat; - - // Utility function that does following: - // execute([ f, self, args...]) => f.apply(self, args) - function execute (args) { - return call.apply(call, args); - } - - // Utility function that takes promise of `a` array and maybe promise `b` - // as arguments and returns promise for `a.concat(b)`. - function promisedConcat(promises, unknown) { - return promises.then(function (values) { - return resolve(unknown) - .then(value => values.concat([value])); - }); - } - - return function promised(f, prototype) { - /** - Returns a wrapped `f`, which when called returns a promise that resolves to - `f(...)` passing all the given arguments to it, which by the way may be - promises. Optionally second `prototype` argument may be provided to be used - a prototype for a returned promise. - - ## Example - - var promise = promised(Array)(1, promise(2), promise(3)) - promise.then(console.log) // => [ 1, 2, 3 ] - **/ - - return function promised(...args) { - // create array of [ f, this, args... ] - return [f, this, ...args]. - // reduce it via `promisedConcat` to get promised array of fulfillments - reduce(promisedConcat, resolve([], prototype)). - // finally map that to promise of `f.apply(this, args...)` - then(execute); - }; - }; -})(); - -exports.promised = promised; -exports.all = all; -exports.defer = defer; -exports.resolve = resolve; -exports.reject = reject; -exports.race = race; -exports.Promise = Promise; -exports.Debugging = Debugging; -}); - -function getEnvironment (callback) { - let Cu, _exports, _module, _require; - - // CommonJS / SDK - if (typeof(require) === 'function') { - Cu = require('chrome').Cu; - _exports = exports; - _module = module; - _require = require; - } - // JSM - else if (String(this).indexOf('BackstagePass') >= 0) { - Cu = this['Components'].utils; - _exports = this.Promise = {}; - _module = { uri: __URI__, id: 'promise/core' }; - _require = uri => { - let imports = {}; - Cu.import(uri, imports); - return imports; - }; - this.EXPORTED_SYMBOLS = ['Promise']; - // mozIJSSubScriptLoader.loadSubscript - } else if (~String(this).indexOf('Sandbox')) { - Cu = this['Components'].utils; - _exports = this; - _module = { id: 'promise/core' }; - _require = uri => {}; - } - - callback({ - Cu: Cu, - exports: _exports, - module: _module, - require: _require - }); -} - diff --git a/addon-sdk/source/lib/sdk/core/reference.js b/addon-sdk/source/lib/sdk/core/reference.js deleted file mode 100644 index 04549cd0f..000000000 --- a/addon-sdk/source/lib/sdk/core/reference.js +++ /dev/null @@ -1,29 +0,0 @@ -/* 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": "experimental" -}; - -const method = require("../../method/core"); -const { Class } = require("./heritage"); - -// Object that inherit or mix WeakRefence inn will register -// weak observes for system notifications. -const WeakReference = Class({}); -exports.WeakReference = WeakReference; - - -// If `isWeak(object)` is `true` observer installed -// for such `object` will be weak, meaning that it will -// be GC-ed if nothing else but observer is observing it. -// By default everything except `WeakReference` will return -// `false`. -const isWeak = method("reference/weak?"); -exports.isWeak = isWeak; - -isWeak.define(Object, _ => false); -isWeak.define(WeakReference, _ => true); diff --git a/addon-sdk/source/lib/sdk/deprecated/api-utils.js b/addon-sdk/source/lib/sdk/deprecated/api-utils.js deleted file mode 100644 index 856fc50cb..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/api-utils.js +++ /dev/null @@ -1,197 +0,0 @@ -/* 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": "deprecated" -}; - -const { merge } = require("../util/object"); -const { union } = require("../util/array"); -const { isNil, isRegExp } = require("../lang/type"); - -// The possible return values of getTypeOf. -const VALID_TYPES = [ - "array", - "boolean", - "function", - "null", - "number", - "object", - "string", - "undefined", - "regexp" -]; - -const { isArray } = Array; - -/** - * Returns a validated options dictionary given some requirements. If any of - * the requirements are not met, an exception is thrown. - * - * @param options - * An object, the options dictionary to validate. It's not modified. - * If it's null or otherwise falsey, an empty object is assumed. - * @param requirements - * An object whose keys are the expected keys in options. Any key in - * options that is not present in requirements is ignored. Each value - * in requirements is itself an object describing the requirements of - * its key. There are four optional keys in this object: - * map: A function that's passed the value of the key in options. - * map's return value is taken as the key's value in the final - * validated options, is, and ok. If map throws an exception - * it's caught and discarded, and the key's value is its value in - * options. - * is: An array containing any number of the typeof type names. If - * the key's value is none of these types, it fails validation. - * Arrays, null and regexps are identified by the special type names - * "array", "null", "regexp"; "object" will not match either. No type - * coercion is done. - * ok: A function that's passed the key's value. If it returns - * false, the value fails validation. - * msg: If the key's value fails validation, an exception is thrown. - * This string will be used as its message. If undefined, a - * generic message is used, unless is is defined, in which case - * the message will state that the value needs to be one of the - * given types. - * @return An object whose keys are those keys in requirements that are also in - * options and whose values are the corresponding return values of map - * or the corresponding values in options. Note that any keys not - * shared by both requirements and options are not in the returned - * object. - */ -exports.validateOptions = function validateOptions(options, requirements) { - options = options || {}; - let validatedOptions = {}; - - for (let key in requirements) { - let isOptional = false; - let mapThrew = false; - let req = requirements[key]; - let [optsVal, keyInOpts] = (key in options) ? - [options[key], true] : - [undefined, false]; - if (req.map) { - try { - optsVal = req.map(optsVal); - } - catch (err) { - if (err instanceof RequirementError) - throw err; - - mapThrew = true; - } - } - if (req.is) { - let types = req.is; - - if (!isArray(types) && isArray(types.is)) - types = types.is; - - if (isArray(types)) { - isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v)); - - // Sanity check the caller's type names. - types.forEach(function (typ) { - if (VALID_TYPES.indexOf(typ) < 0) { - let msg = 'Internal error: invalid requirement type "' + typ + '".'; - throw new Error(msg); - } - }); - if (types.indexOf(getTypeOf(optsVal)) < 0) - throw new RequirementError(key, req); - } - } - - if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal))) - throw new RequirementError(key, req); - - if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined)) - validatedOptions[key] = optsVal; - } - - return validatedOptions; -}; - -exports.addIterator = function addIterator(obj, keysValsGenerator) { - obj.__iterator__ = function(keysOnly, keysVals) { - let keysValsIterator = keysValsGenerator.call(this); - - // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values, - // and "for (.. in Iterator(..))" gets [key, value] pairs. - let index = keysOnly ? 0 : 1; - while (true) - yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index]; - }; -}; - -// Similar to typeof, except arrays, null and regexps are identified by "array" and -// "null" and "regexp", not "object". -var getTypeOf = exports.getTypeOf = function getTypeOf(val) { - let typ = typeof(val); - if (typ === "object") { - if (!val) - return "null"; - if (isArray(val)) - return "array"; - if (isRegExp(val)) - return "regexp"; - } - return typ; -} - -function RequirementError(key, requirement) { - Error.call(this); - - this.name = "RequirementError"; - - let msg = requirement.msg; - if (!msg) { - msg = 'The option "' + key + '" '; - msg += requirement.is ? - "must be one of the following types: " + requirement.is.join(", ") : - "is invalid."; - } - - this.message = msg; -} -RequirementError.prototype = Object.create(Error.prototype); - -var string = { is: ['string', 'undefined', 'null'] }; -exports.string = string; - -var number = { is: ['number', 'undefined', 'null'] }; -exports.number = number; - -var boolean = { is: ['boolean', 'undefined', 'null'] }; -exports.boolean = boolean; - -var object = { is: ['object', 'undefined', 'null'] }; -exports.object = object; - -var array = { is: ['array', 'undefined', 'null'] }; -exports.array = array; - -var isTruthyType = type => !(type === 'undefined' || type === 'null'); -var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v }; - -function required(req) { - let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType); - - return merge({}, req, {is: types}); -} -exports.required = required; - -function optional(req) { - req = merge({is: []}, req); - req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null'); - - return req; -} -exports.optional = optional; - -function either(...types) { - return union.apply(null, types.map(findTypes)); -} -exports.either = either; diff --git a/addon-sdk/source/lib/sdk/deprecated/events/assembler.js b/addon-sdk/source/lib/sdk/deprecated/events/assembler.js deleted file mode 100644 index bb297c24f..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/events/assembler.js +++ /dev/null @@ -1,54 +0,0 @@ -/* 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 { removeListener, on } = require("../../dom/events"); - -/** - * Event targets - * can be added / removed by calling `observe / ignore` methods. Composer should - * provide array of event types it wishes to handle as property - * `supportedEventsTypes` and function for handling all those events as - * `handleEvent` property. - */ -exports.DOMEventAssembler = Class({ - /** - * Function that is supposed to handle all the supported events (that are - * present in the `supportedEventsTypes`) from all the observed - * `eventTargets`. - * @param {Event} event - * Event being dispatched. - */ - handleEvent() { - throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method"); - }, - /** - * Array of supported event names. - * @type {String[]} - */ - get supportedEventsTypes() { - throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field"); - }, - /** - * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for - * supported events will be registered on the given `eventTarget`. - * @param {EventTarget} eventTarget - */ - observe: function observe(eventTarget) { - this.supportedEventsTypes.forEach(function(eventType) { - on(eventTarget, eventType, this); - }, this); - }, - /** - * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners - * for all supported events will be unregistered from the given `eventTarget`. - * @param {EventTarget} eventTarget - */ - ignore: function ignore(eventTarget) { - this.supportedEventsTypes.forEach(function(eventType) { - removeListener(eventTarget, eventType, this); - }, this); - } -}); diff --git a/addon-sdk/source/lib/sdk/deprecated/sync-worker.js b/addon-sdk/source/lib/sdk/deprecated/sync-worker.js deleted file mode 100644 index 71cadac36..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/sync-worker.js +++ /dev/null @@ -1,288 +0,0 @@ -/* 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/. */ - -/** - * - * `deprecated/sync-worker` was previously `content/worker`, that was - * incompatible with e10s. we are in the process of switching to the new - * asynchronous `Worker`, which behaves slightly differently in some edge - * cases, so we are keeping this one around for a short period. - * try to switch to the new one as soon as possible.. - * - */ - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { on, off, emit, setListeners } = require('../event/core'); -const { - attach, detach, destroy -} = require('../content/utils'); -const { method } = require('../lang/functional'); -const { Ci, Cu, Cc } = require('chrome'); -const unload = require('../system/unload'); -const events = require('../system/events'); -const { getInnerId } = require("../window/utils"); -const { WorkerSandbox } = require('../content/sandbox'); -const { isPrivate } = require('../private-browsing/utils'); - -// A weak map of workers to hold private attributes that -// should not be exposed -const workers = new WeakMap(); - -var modelFor = (worker) => workers.get(worker); - -const ERR_DESTROYED = - "Couldn't find the worker to receive this message. " + - "The script may not be initialized yet, or may already have been unloaded."; - -const ERR_FROZEN = "The page is currently hidden and can no longer be used " + - "until it is visible again."; - -/** - * Message-passing facility for communication between code running - * in the content and add-on process. - * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker - */ -const Worker = Class({ - implements: [EventTarget], - initialize: function WorkerConstructor (options) { - // Save model in weak map to not expose properties - let model = createModel(); - workers.set(this, model); - - options = options || {}; - - if ('contentScriptFile' in options) - this.contentScriptFile = options.contentScriptFile; - if ('contentScriptOptions' in options) - this.contentScriptOptions = options.contentScriptOptions; - if ('contentScript' in options) - this.contentScript = options.contentScript; - if ('injectInDocument' in options) - this.injectInDocument = !!options.injectInDocument; - - setListeners(this, options); - - unload.ensure(this, "destroy"); - - // Ensure that worker.port is initialized for contentWorker to be able - // to send events during worker initialization. - this.port = createPort(this); - - model.documentUnload = documentUnload.bind(this); - model.pageShow = pageShow.bind(this); - model.pageHide = pageHide.bind(this); - - if ('window' in options) - attach(this, options.window); - }, - - /** - * Sends a message to the worker's global scope. Method takes single - * argument, which represents data to be sent to the worker. The data may - * be any primitive type value or `JSON`. Call of this method asynchronously - * emits `message` event with data value in the global scope of this - * worker. - * - * `message` event listeners can be set either by calling - * `self.on` with a first argument string `"message"` or by - * implementing `onMessage` function in the global scope of this worker. - * @param {Number|String|JSON} data - */ - postMessage: function (...data) { - let model = modelFor(this); - let args = ['message'].concat(data); - if (!model.inited) { - model.earlyEvents.push(args); - return; - } - processMessage.apply(null, [this].concat(args)); - }, - - get url () { - let model = modelFor(this); - // model.window will be null after detach - return model.window ? model.window.document.location.href : null; - }, - - get contentURL () { - let model = modelFor(this); - return model.window ? model.window.document.URL : null; - }, - - // Implemented to provide some of the previous features of exposing sandbox - // so that Worker can be extended - getSandbox: function () { - return modelFor(this).contentWorker; - }, - - toString: function () { return '[object Worker]'; }, - attach: method(attach), - detach: method(detach), - destroy: method(destroy) -}); -exports.Worker = Worker; - -attach.define(Worker, function (worker, window) { - let model = modelFor(worker); - model.window = window; - // Track document unload to destroy this worker. - // We can't watch for unload event on page's window object as it - // prevents bfcache from working: - // https://developer.mozilla.org/En/Working_with_BFCache - model.windowID = getInnerId(model.window); - events.on("inner-window-destroyed", model.documentUnload); - - // will set model.contentWorker pointing to the private API: - model.contentWorker = WorkerSandbox(worker, model.window); - - // Listen to pagehide event in order to freeze the content script - // while the document is frozen in bfcache: - model.window.addEventListener("pageshow", model.pageShow, true); - model.window.addEventListener("pagehide", model.pageHide, true); - - // Mainly enable worker.port.emit to send event to the content worker - model.inited = true; - model.frozen = false; - - // Fire off `attach` event - emit(worker, 'attach', window); - - // Process all events and messages that were fired before the - // worker was initialized. - model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args))); -}); - -/** - * Remove all internal references to the attached document - * Tells _port to unload itself and removes all the references from itself. - */ -detach.define(Worker, function (worker, reason) { - let model = modelFor(worker); - - // maybe unloaded before content side is created - if (model.contentWorker) { - model.contentWorker.destroy(reason); - } - - model.contentWorker = null; - if (model.window) { - model.window.removeEventListener("pageshow", model.pageShow, true); - model.window.removeEventListener("pagehide", model.pageHide, true); - } - model.window = null; - // This method may be called multiple times, - // avoid dispatching `detach` event more than once - if (model.windowID) { - model.windowID = null; - events.off("inner-window-destroyed", model.documentUnload); - model.earlyEvents.length = 0; - emit(worker, 'detach'); - } - model.inited = false; -}); - -isPrivate.define(Worker, ({ tab }) => isPrivate(tab)); - -/** - * Tells content worker to unload itself and - * removes all the references from itself. - */ -destroy.define(Worker, function (worker, reason) { - detach(worker, reason); - modelFor(worker).inited = true; - // Specifying no type or listener removes all listeners - // from target - off(worker); - off(worker.port); -}); - -/** - * Events fired by workers - */ -function documentUnload ({ subject, data }) { - let model = modelFor(this); - let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (innerWinID != model.windowID) return false; - detach(this); - return true; -} - -function pageShow () { - let model = modelFor(this); - model.contentWorker.emitSync('pageshow'); - emit(this, 'pageshow'); - model.frozen = false; -} - -function pageHide () { - let model = modelFor(this); - model.contentWorker.emitSync('pagehide'); - emit(this, 'pagehide'); - model.frozen = true; -} - -/** - * Fired from postMessage and emitEventToContent, or from the earlyMessage - * queue when fired before the content is loaded. Sends arguments to - * contentWorker if able - */ - -function processMessage (worker, ...args) { - let model = modelFor(worker) || {}; - if (!model.contentWorker) - throw new Error(ERR_DESTROYED); - if (model.frozen) - throw new Error(ERR_FROZEN); - model.contentWorker.emit.apply(null, args); -} - -function createModel () { - return { - // List of messages fired before worker is initialized - earlyEvents: [], - // Is worker connected to the content worker sandbox ? - inited: false, - // Is worker being frozen? i.e related document is frozen in bfcache. - // Content script should not be reachable if frozen. - frozen: true, - /** - * Reference to the content side of the worker. - * @type {WorkerGlobalScope} - */ - contentWorker: null, - /** - * Reference to the window that is accessible from - * the content scripts. - * @type {Object} - */ - window: null - }; -} - -function createPort (worker) { - let port = EventTarget(); - port.emit = emitEventToContent.bind(null, worker); - return port; -} - -/** - * Emit a custom event to the content script, - * i.e. emit this event on `self.port` - */ -function emitEventToContent (worker, ...eventArgs) { - let model = modelFor(worker); - let args = ['event'].concat(eventArgs); - if (!model.inited) { - model.earlyEvents.push(args); - return; - } - processMessage.apply(null, [worker].concat(args)); -} diff --git a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js b/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js deleted file mode 100644 index e38629f45..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js +++ /dev/null @@ -1,199 +0,0 @@ -/* 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": "deprecated" -}; - -const file = require("../io/file"); -const { Loader } = require("../test/loader"); - -const { isNative } = require('@loader/options'); - -const cuddlefish = isNative ? require("toolkit/loader") : require("../loader/cuddlefish"); - -const { defer, resolve } = require("../core/promise"); -const { getAddon } = require("../addon/installer"); -const { id } = require("sdk/self"); -const { newURI } = require('sdk/url/utils'); -const { getZipReader } = require("../zip/utils"); - -const { Cc, Ci, Cu } = require("chrome"); -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); -var ios = Cc['@mozilla.org/network/io-service;1'] - .getService(Ci.nsIIOService); - -const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/; -const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/; - -const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence"); - -const toFile = x => x.QueryInterface(Ci.nsIFile); -const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js"; -const getFileURI = x => ios.newFileURI(x).spec; - -const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries)); -const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory)); -const getTestURIs = directory => map(getFileURI, getTestFiles(directory)); - -const isDirectory = x => x.isDirectory(); -const getTestEntries = directory => mapcat(entry => - /^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry), - filter(isDirectory, getDirectoryEntries(directory))); - -const removeDups = (array) => array.reduce((result, value) => { - if (value != result[result.length - 1]) { - result.push(value); - } - return result; -}, []); - -const getSuites = function getSuites({ id, filter }) { - const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX; - - return getAddon(id).then(addon => { - let fileURI = addon.getResourceURI("tests/"); - let isPacked = fileURI.scheme == "jar"; - let xpiURI = addon.getResourceURI(); - let file = xpiURI.QueryInterface(Ci.nsIFileURL).file; - let suites = []; - let addEntry = (entry) => { - if (filter(entry) && TEST_REGEX.test(entry)) { - let suite = (isNative ? "./" : "") + (RegExp.$2 || "") + RegExp.$3; - suites.push(suite); - } - } - - if (isPacked) { - return getZipReader(file).then(zip => { - let entries = zip.findEntries(null); - while (entries.hasMore()) { - let entry = entries.getNext(); - addEntry(entry); - } - zip.close(); - - // sort and remove dups - suites = removeDups(suites.sort()); - return suites; - }) - } - else { - let tests = [...getTestEntries(file)]; - let rootURI = addon.getResourceURI("/"); - tests.forEach((entry) => { - addEntry(entry.replace(rootURI.spec, "")); - }); - } - - // sort and remove dups - suites = removeDups(suites.sort()); - return suites; - }); -} -exports.getSuites = getSuites; - -const makeFilters = function makeFilters(options) { - options = options || {}; - - // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon - // optionally separates a regex for the test fileName from a regex for the - // testName. - if (options.filter) { - let colonPos = options.filter.indexOf(':'); - let filterFileRegex, filterNameRegex; - - if (colonPos === -1) { - filterFileRegex = new RegExp(options.filter); - filterNameRegex = { test: () => true } - } - else { - filterFileRegex = new RegExp(options.filter.substr(0, colonPos)); - filterNameRegex = new RegExp(options.filter.substr(colonPos + 1)); - } - - return { - fileFilter: (name) => filterFileRegex.test(name), - testFilter: (name) => filterNameRegex.test(name) - } - } - - return { - fileFilter: () => true, - testFilter: () => true - }; -} -exports.makeFilters = makeFilters; - -var loader = Loader(module); -const NOT_TESTS = ['setup', 'teardown']; - -var TestFinder = exports.TestFinder = function TestFinder(options) { - this.filter = options.filter; - this.testInProcess = options.testInProcess === false ? false : true; - this.testOutOfProcess = options.testOutOfProcess === true ? true : false; -}; - -TestFinder.prototype = { - findTests: function findTests() { - let { fileFilter, testFilter } = makeFilters({ filter: this.filter }); - - return getSuites({ id: id, filter: fileFilter }).then(suites => { - let testsRemaining = []; - - let getNextTest = () => { - if (testsRemaining.length) { - return testsRemaining.shift(); - } - - if (!suites.length) { - return null; - } - - let suite = suites.shift(); - - // Load each test file as a main module in its own loader instance - // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build - let suiteModule; - - try { - suiteModule = cuddlefish.main(loader, suite); - } - catch (e) { - if (/Unsupported Application/i.test(e.message)) { - // If `Unsupported Application` error thrown during test, - // skip the test suite - suiteModule = { - 'test suite skipped': assert => assert.pass(e.message) - }; - } - else { - console.exception(e); - throw e; - } - } - - if (this.testInProcess) { - for (let name of Object.keys(suiteModule).sort()) { - if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) { - testsRemaining.push({ - setup: suiteModule.setup, - teardown: suiteModule.teardown, - testFunction: suiteModule[name], - name: suite + "." + name - }); - } - } - } - - return getNextTest(); - }; - - return { - getNext: () => resolve(getNextTest()) - }; - }); - } -}; diff --git a/addon-sdk/source/lib/sdk/deprecated/unit-test.js b/addon-sdk/source/lib/sdk/deprecated/unit-test.js deleted file mode 100644 index 32bba8f6b..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js +++ /dev/null @@ -1,584 +0,0 @@ -/* 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": "deprecated" -}; - -const timer = require("../timers"); -const cfxArgs = require("../test/options"); -const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils"); -const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils"); -const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise"); -const { getInnerId } = require("../window/utils"); -const { cleanUI } = require("../test/utils"); - -const findAndRunTests = function findAndRunTests(options) { - var TestFinder = require("./unit-test-finder").TestFinder; - var finder = new TestFinder({ - filter: options.filter, - testInProcess: options.testInProcess, - testOutOfProcess: options.testOutOfProcess - }); - var runner = new TestRunner({fs: options.fs}); - finder.findTests().then(tests => { - runner.startMany({ - tests: tests, - stopOnError: options.stopOnError, - onDone: options.onDone - }); - }); -}; -exports.findAndRunTests = findAndRunTests; - -var runnerWindows = new WeakMap(); -var runnerTabs = new WeakMap(); - -const TestRunner = function TestRunner(options) { - options = options || {}; - - // remember the id's for the open window and tab - let window = getMostRecentBrowserWindow(); - runnerWindows.set(this, getInnerId(window)); - runnerTabs.set(this, getTabId(getSelectedTab(window))); - - this.fs = options.fs; - this.console = options.console || console; - this.passed = 0; - this.failed = 0; - this.testRunSummary = []; - this.expectFailNesting = 0; - this.done = TestRunner.prototype.done.bind(this); -}; - -TestRunner.prototype = { - toString: function toString() { - return "[object TestRunner]"; - }, - - DEFAULT_PAUSE_TIMEOUT: (cfxArgs.parseable ? 300000 : 15000), //Five minutes (5*60*1000ms) - PAUSE_DELAY: 500, - - _logTestFailed: function _logTestFailed(why) { - if (!(why in this.test.errors)) - this.test.errors[why] = 0; - this.test.errors[why]++; - }, - - _uncaughtErrorObserver: function({message, date, fileName, stack, lineNumber}) { - this.fail("There was an uncaught Promise rejection: " + message + " @ " + - fileName + ":" + lineNumber + "\n" + stack); - }, - - pass: function pass(message) { - if(!this.expectFailure) { - if ("testMessage" in this.console) - this.console.testMessage(true, true, this.test.name, message); - else - this.console.info("pass:", message); - this.passed++; - this.test.passed++; - this.test.last = message; - } - else { - this.expectFailure = false; - this._logTestFailed("failure"); - if ("testMessage" in this.console) { - this.console.testMessage(true, false, this.test.name, message); - } - else { - this.console.error("fail:", 'Failure Expected: ' + message) - this.console.trace(); - } - this.failed++; - this.test.failed++; - } - }, - - fail: function fail(message) { - if(!this.expectFailure) { - this._logTestFailed("failure"); - if ("testMessage" in this.console) { - this.console.testMessage(false, false, this.test.name, message); - } - else { - this.console.error("fail:", message) - this.console.trace(); - } - this.failed++; - this.test.failed++; - } - else { - this.expectFailure = false; - if ("testMessage" in this.console) - this.console.testMessage(false, true, this.test.name, message); - else - this.console.info("pass:", message); - this.passed++; - this.test.passed++; - this.test.last = message; - } - }, - - expectFail: function(callback) { - this.expectFailure = true; - callback(); - this.expectFailure = false; - }, - - exception: function exception(e) { - this._logTestFailed("exception"); - if (cfxArgs.parseable) - this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n"); - this.console.exception(e); - this.failed++; - this.test.failed++; - }, - - assertMatches: function assertMatches(string, regexp, message) { - if (regexp.test(string)) { - if (!message) - message = uneval(string) + " matches " + uneval(regexp); - this.pass(message); - } else { - var no = uneval(string) + " doesn't match " + uneval(regexp); - if (!message) - message = no; - else - message = message + " (" + no + ")"; - this.fail(message); - } - }, - - assertRaises: function assertRaises(func, predicate, message) { - try { - func(); - if (message) - this.fail(message + " (no exception thrown)"); - else - this.fail("function failed to throw exception"); - } catch (e) { - var errorMessage; - if (typeof(e) == "string") - errorMessage = e; - else - errorMessage = e.message; - if (typeof(predicate) == "string") - this.assertEqual(errorMessage, predicate, message); - else - this.assertMatches(errorMessage, predicate, message); - } - }, - - assert: function assert(a, message) { - if (!a) { - if (!message) - message = "assertion failed, value is " + a; - this.fail(message); - } else - this.pass(message || "assertion successful"); - }, - - assertNotEqual: function assertNotEqual(a, b, message) { - if (a != b) { - if (!message) - message = "a != b != " + uneval(a); - this.pass(message); - } else { - var equality = uneval(a) + " == " + uneval(b); - if (!message) - message = equality; - else - message += " (" + equality + ")"; - this.fail(message); - } - }, - - assertEqual: function assertEqual(a, b, message) { - if (a == b) { - if (!message) - message = "a == b == " + uneval(a); - this.pass(message); - } else { - var inequality = uneval(a) + " != " + uneval(b); - if (!message) - message = inequality; - else - message += " (" + inequality + ")"; - this.fail(message); - } - }, - - assertNotStrictEqual: function assertNotStrictEqual(a, b, message) { - if (a !== b) { - if (!message) - message = "a !== b !== " + uneval(a); - this.pass(message); - } else { - var equality = uneval(a) + " === " + uneval(b); - if (!message) - message = equality; - else - message += " (" + equality + ")"; - this.fail(message); - } - }, - - assertStrictEqual: function assertStrictEqual(a, b, message) { - if (a === b) { - if (!message) - message = "a === b === " + uneval(a); - this.pass(message); - } else { - var inequality = uneval(a) + " !== " + uneval(b); - if (!message) - message = inequality; - else - message += " (" + inequality + ")"; - this.fail(message); - } - }, - - assertFunction: function assertFunction(a, message) { - this.assertStrictEqual('function', typeof a, message); - }, - - assertUndefined: function(a, message) { - this.assertStrictEqual('undefined', typeof a, message); - }, - - assertNotUndefined: function(a, message) { - this.assertNotStrictEqual('undefined', typeof a, message); - }, - - assertNull: function(a, message) { - this.assertStrictEqual(null, a, message); - }, - - assertNotNull: function(a, message) { - this.assertNotStrictEqual(null, a, message); - }, - - assertObject: function(a, message) { - this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message); - }, - - assertString: function(a, message) { - this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message); - }, - - assertArray: function(a, message) { - this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message); - }, - - assertNumber: function(a, message) { - this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message); - }, - - done: function done() { - if (this.isDone) { - return resolve(); - } - - this.isDone = true; - this.pass("This test is done."); - - if (this.test.teardown) { - this.test.teardown(this); - } - - if (this.waitTimeout !== null) { - timer.clearTimeout(this.waitTimeout); - this.waitTimeout = null; - } - - // Do not leave any callback set when calling to `waitUntil` - this.waitUntilCallback = null; - if (this.test.passed == 0 && this.test.failed == 0) { - this._logTestFailed("empty test"); - - if ("testMessage" in this.console) { - this.console.testMessage(false, false, this.test.name, "Empty test"); - } - else { - this.console.error("fail:", "Empty test") - } - - this.failed++; - this.test.failed++; - } - - let wins = windows(null, { includePrivate: true }); - let winPromises = wins.map(win => { - return new Promise(resolve => { - if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) { - resolve() - } - else { - win.addEventListener("DOMContentLoaded", function onLoad() { - win.removeEventListener("DOMContentLoaded", onLoad, false); - resolve(); - }, false); - } - }); - }); - - PromiseDebugging.flushUncaughtErrors(); - PromiseDebugging.removeUncaughtErrorObserver(this._uncaughtErrorObserver); - - - return all(winPromises).then(() => { - let browserWins = wins.filter(isBrowser); - let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []); - let newTabID = getTabId(getSelectedTab(wins[0])); - let oldTabID = runnerTabs.get(this); - let hasMoreTabsOpen = browserWins.length && tabs.length != 1; - let failure = false; - - if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) { - failure = true; - this.fail("Should not be any unexpected windows open"); - } - else if (hasMoreTabsOpen) { - failure = true; - this.fail("Should not be any unexpected tabs open"); - } - else if (oldTabID != newTabID) { - failure = true; - runnerTabs.set(this, newTabID); - this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID); - } - - if (failure) { - console.log("Windows open:"); - for (let win of wins) { - if (isBrowser(win)) { - tabs = getTabs(win); - console.log(win.location + " - " + tabs.map(getURI).join(", ")); - } - else { - console.log(win.location); - } - } - } - - return failure; - }). - then(failure => { - if (!failure) { - this.pass("There was a clean UI."); - return null; - } - return cleanUI().then(() => { - this.pass("There is a clean UI."); - }); - }). - then(() => { - this.testRunSummary.push({ - name: this.test.name, - passed: this.test.passed, - failed: this.test.failed, - errors: Object.keys(this.test.errors).join(", ") - }); - - if (this.onDone !== null) { - let onDone = this.onDone; - this.onDone = null; - timer.setTimeout(_ => onDone(this)); - } - }). - catch(console.exception); - }, - - // Set of assertion functions to wait for an assertion to become true - // These functions take the same arguments as the TestRunner.assert* methods. - waitUntil: function waitUntil() { - return this._waitUntil(this.assert, arguments); - }, - - waitUntilNotEqual: function waitUntilNotEqual() { - return this._waitUntil(this.assertNotEqual, arguments); - }, - - waitUntilEqual: function waitUntilEqual() { - return this._waitUntil(this.assertEqual, arguments); - }, - - waitUntilMatches: function waitUntilMatches() { - return this._waitUntil(this.assertMatches, arguments); - }, - - /** - * Internal function that waits for an assertion to become true. - * @param {Function} assertionMethod - * Reference to a TestRunner assertion method like test.assert, - * test.assertEqual, ... - * @param {Array} args - * List of arguments to give to the previous assertion method. - * All functions in this list are going to be called to retrieve current - * assertion values. - */ - _waitUntil: function waitUntil(assertionMethod, args) { - let { promise, resolve } = defer(); - let count = 0; - let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY; - - // We need to ensure that test is asynchronous - if (!this.waitTimeout) - this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT); - - let finished = false; - let test = this; - - // capture a traceback before we go async. - let traceback = require("../console/traceback"); - let stack = traceback.get(); - stack.splice(-2, 2); - let currentWaitStack = traceback.format(stack); - let timeout = null; - - function loop(stopIt) { - timeout = null; - - // Build a mockup object to fake TestRunner API and intercept calls to - // pass and fail methods, in order to retrieve nice error messages - // and assertion result - let mock = { - pass: function (msg) { - test.pass(msg); - test.waitUntilCallback = null; - if (!stopIt) - resolve(); - }, - fail: function (msg) { - // If we are called on test timeout, we stop the loop - // and print which test keeps failing: - if (stopIt) { - test.console.error("test assertion never became true:\n", - msg + "\n", - currentWaitStack); - if (timeout) - timer.clearTimeout(timeout); - return; - } - timeout = timer.setTimeout(loop, test.PAUSE_DELAY); - } - }; - - // Automatically call args closures in order to build arguments for - // assertion function - let appliedArgs = []; - for (let i = 0, l = args.length; i < l; i++) { - let a = args[i]; - if (typeof a == "function") { - try { - a = a(); - } - catch(e) { - test.fail("Exception when calling asynchronous assertion: " + e + - "\n" + e.stack); - return resolve(); - } - } - appliedArgs.push(a); - } - - // Finally call assertion function with current assertion values - assertionMethod.apply(mock, appliedArgs); - } - loop(); - this.waitUntilCallback = loop; - - return promise; - }, - - waitUntilDone: function waitUntilDone(ms) { - if (ms === undefined) - ms = this.DEFAULT_PAUSE_TIMEOUT; - - var self = this; - - function tiredOfWaiting() { - self._logTestFailed("timed out"); - if ("testMessage" in self.console) { - self.console.testMessage(false, false, self.test.name, - `Test timed out (after: ${self.test.last})`); - } - else { - self.console.error("fail:", `Timed out (after: ${self.test.last})`) - } - if (self.waitUntilCallback) { - self.waitUntilCallback(true); - self.waitUntilCallback = null; - } - self.failed++; - self.test.failed++; - self.done(); - } - - // We may already have registered a timeout callback - if (this.waitTimeout) - timer.clearTimeout(this.waitTimeout); - - this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms); - }, - - startMany: function startMany(options) { - function runNextTest(self) { - let { tests, onDone } = options; - - return tests.getNext().then((test) => { - if (options.stopOnError && self.test && self.test.failed) { - self.console.error("aborted: test failed and --stop-on-error was specified"); - onDone(self); - } - else if (test) { - self.start({test: test, onDone: runNextTest}); - } - else { - onDone(self); - } - }); - } - - return runNextTest(this).catch(console.exception); - }, - - start: function start(options) { - this.test = options.test; - this.test.passed = 0; - this.test.failed = 0; - this.test.errors = {}; - this.test.last = 'START'; - PromiseDebugging.clearUncaughtErrorObservers(); - this._uncaughtErrorObserver = this._uncaughtErrorObserver.bind(this); - PromiseDebugging.addUncaughtErrorObserver(this._uncaughtErrorObserver); - - this.isDone = false; - this.onDone = function(self) { - if (cfxArgs.parseable) - self.console.print("TEST-END | " + self.test.name + "\n"); - options.onDone(self); - } - this.waitTimeout = null; - - try { - if (cfxArgs.parseable) - this.console.print("TEST-START | " + this.test.name + "\n"); - else - this.console.info("executing '" + this.test.name + "'"); - - if(this.test.setup) { - this.test.setup(this); - } - this.test.testFunction(this); - } catch (e) { - this.exception(e); - } - if (this.waitTimeout === null) - this.done(); - } -}; -exports.TestRunner = TestRunner; diff --git a/addon-sdk/source/lib/sdk/deprecated/window-utils.js b/addon-sdk/source/lib/sdk/deprecated/window-utils.js deleted file mode 100644 index 93c0ab7b8..000000000 --- a/addon-sdk/source/lib/sdk/deprecated/window-utils.js +++ /dev/null @@ -1,193 +0,0 @@ -/* 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': 'deprecated' -}; - -const { Cc, Ci } = require('chrome'); -const events = require('../system/events'); -const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser, - getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils'); -const { deprecateFunction } = require('../util/deprecate'); -const { ignoreWindow } = require('sdk/private-browsing/utils'); -const { isPrivateBrowsingSupported } = require('../self'); - -const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. - getService(Ci.nsIWindowWatcher); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); - -// Bug 834961: ignore private windows when they are not supported -function getWindows() { - return windows(null, { includePrivate: isPrivateBrowsingSupported }); -} - -/** - * An iterator for XUL windows currently in the application. - * - * @return A generator that yields XUL windows exposing the - * nsIDOMWindow interface. - */ -function windowIterator() { - // Bug 752631: We only pass already loaded window in order to avoid - // breaking XUL windows DOM. DOM is broken when some JS code try - // to access DOM during "uninitialized" state of the related document. - let list = getWindows().filter(isDocumentLoaded); - for (let i = 0, l = list.length; i < l; i++) { - yield list[i]; - } -}; -exports.windowIterator = windowIterator; - -/** - * An iterator for browser windows currently open in the application. - * @returns {Function} - * A generator that yields browser windows exposing the `nsIDOMWindow` - * interface. - */ -function browserWindowIterator() { - for (let window of windowIterator()) { - if (isBrowser(window)) - yield window; - } -} -exports.browserWindowIterator = browserWindowIterator; - -function WindowTracker(delegate) { - if (!(this instanceof WindowTracker)) { - return new WindowTracker(delegate); - } - - this._delegate = delegate; - - for (let window of getWindows()) - this._regWindow(window); - windowWatcher.registerNotification(this); - this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this); - events.on('toplevel-window-ready', this._onToplevelWindowReady); - - require('../system/unload').ensure(this); - - return this; -}; - -WindowTracker.prototype = { - _regLoadingWindow: function _regLoadingWindow(window) { - // Bug 834961: ignore private windows when they are not supported - if (ignoreWindow(window)) - return; - - window.addEventListener('load', this, true); - }, - - _unregLoadingWindow: function _unregLoadingWindow(window) { - // This may have no effect if we ignored the window in _regLoadingWindow(). - window.removeEventListener('load', this, true); - }, - - _regWindow: function _regWindow(window) { - // Bug 834961: ignore private windows when they are not supported - if (ignoreWindow(window)) - return; - - if (window.document.readyState == 'complete') { - this._unregLoadingWindow(window); - this._delegate.onTrack(window); - } else - this._regLoadingWindow(window); - }, - - _unregWindow: function _unregWindow(window) { - if (window.document.readyState == 'complete') { - if (this._delegate.onUntrack) - this._delegate.onUntrack(window); - } else { - this._unregLoadingWindow(window); - } - }, - - unload: function unload() { - windowWatcher.unregisterNotification(this); - events.off('toplevel-window-ready', this._onToplevelWindowReady); - for (let window of getWindows()) - this._unregWindow(window); - }, - - handleEvent: function handleEvent(event) { - try { - if (event.type == 'load' && event.target) { - var window = event.target.defaultView; - if (window) - this._regWindow(getToplevelWindow(window)); - } - } - catch(e) { - console.exception(e); - } - }, - - _onToplevelWindowReady: function _onToplevelWindowReady({subject}) { - let window = getToplevelWindow(subject); - // ignore private windows if they are not supported - if (ignoreWindow(window)) - return; - this._regWindow(window); - }, - - observe: function observe(subject, topic, data) { - try { - var window = subject.QueryInterface(Ci.nsIDOMWindow); - // ignore private windows if they are not supported - if (ignoreWindow(window)) - return; - if (topic == 'domwindowclosed') - this._unregWindow(window); - } - catch(e) { - console.exception(e); - } - } -}; -exports.WindowTracker = WindowTracker; - -Object.defineProperties(exports, { - activeWindow: { - enumerable: true, - get: function() { - return getMostRecentWindow(null); - }, - set: function(window) { - try { - window.focus(); - } catch (e) {} - } - }, - activeBrowserWindow: { - enumerable: true, - get: getMostRecentBrowserWindow - } -}); - - -/** - * Returns the ID of the window's current inner window. - */ -exports.getInnerId = deprecateFunction(getInnerId, - 'require("window-utils").getInnerId is deprecated, ' + - 'please use require("sdk/window/utils").getInnerId instead' -); - -exports.getOuterId = deprecateFunction(getOuterId, - 'require("window-utils").getOuterId is deprecated, ' + - 'please use require("sdk/window/utils").getOuterId instead' -); - -exports.isBrowser = deprecateFunction(isBrowser, - 'require("window-utils").isBrowser is deprecated, ' + - 'please use require("sdk/window/utils").isBrowser instead' -); - -exports.hiddenWindow = appShellService.hiddenDOMWindow; diff --git a/addon-sdk/source/lib/sdk/dom/events-shimmed.js b/addon-sdk/source/lib/sdk/dom/events-shimmed.js deleted file mode 100644 index 7a1727681..000000000 --- a/addon-sdk/source/lib/sdk/dom/events-shimmed.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 events = require('./events.js'); - -exports.emit = (element, type, obj) => events.emit(element, type, obj, true); -exports.on = (element, type, listener, capture) => events.on(element, type, listener, capture, true); -exports.once = (element, type, listener, capture) => events.once(element, type, listener, capture, true); -exports.removeListener = (element, type, listener, capture) => events.removeListener(element, type, listener, capture, true); -exports.removed = events.removed; -exports.when = (element, eventName, capture) => events.when(element, eventName, capture ? capture : false, true); diff --git a/addon-sdk/source/lib/sdk/dom/events.js b/addon-sdk/source/lib/sdk/dom/events.js deleted file mode 100644 index 502d2350f..000000000 --- a/addon-sdk/source/lib/sdk/dom/events.js +++ /dev/null @@ -1,192 +0,0 @@ -/* 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 { Cu } = require("chrome"); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); - -// Utility function that returns copy of the given `text` with last character -// removed if it is `"s"`. -function singularify(text) { - return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text; -} - -// Utility function that takes event type, argument is passed to -// `document.createEvent` and returns name of the initializer method of the -// given event. Please note that there are some event types whose initializer -// methods can't be guessed by this function. For more details see following -// link: https://developer.mozilla.org/En/DOM/Document.createEvent -function getInitializerName(category) { - return "init" + singularify(category); -} - -/** - * Registers an event `listener` on a given `element`, that will be called - * when events of specified `type` is dispatched on the `element`. - * @param {Element} element - * Dom element to register listener on. - * @param {String} type - * A string representing the - * [event type](https://developer.mozilla.org/en/DOM/event.type) to - * listen for. - * @param {Function} listener - * Function that is called whenever an event of the specified `type` - * occurs. - * @param {Boolean} capture - * If true, indicates that the user wishes to initiate capture. After - * initiating capture, all events of the specified type will be dispatched - * to the registered listener before being dispatched to any `EventTarget`s - * beneath it in the DOM tree. Events which are bubbling upward through - * the tree will not trigger a listener designated to use capture. - * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow) - * for a detailed explanation. - */ -function on(element, type, listener, capture, shimmed = false) { - // `capture` defaults to `false`. - capture = capture || false; - if (shimmed) { - element.addEventListener(type, listener, capture); - } else { - ShimWaiver.getProperty(element, "addEventListener")(type, listener, capture); - } -} -exports.on = on; - -/** - * Registers an event `listener` on a given `element`, that will be called - * only once, next time event of specified `type` is dispatched on the - * `element`. - * @param {Element} element - * Dom element to register listener on. - * @param {String} type - * A string representing the - * [event type](https://developer.mozilla.org/en/DOM/event.type) to - * listen for. - * @param {Function} listener - * Function that is called whenever an event of the specified `type` - * occurs. - * @param {Boolean} capture - * If true, indicates that the user wishes to initiate capture. After - * initiating capture, all events of the specified type will be dispatched - * to the registered listener before being dispatched to any `EventTarget`s - * beneath it in the DOM tree. Events which are bubbling upward through - * the tree will not trigger a listener designated to use capture. - * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow) - * for a detailed explanation. - */ -function once(element, type, listener, capture, shimmed = false) { - on(element, type, function selfRemovableListener(event) { - removeListener(element, type, selfRemovableListener, capture, shimmed); - listener.apply(this, arguments); - }, capture, shimmed); -} -exports.once = once; - -/** - * Unregisters an event `listener` on a given `element` for the events of the - * specified `type`. - * - * @param {Element} element - * Dom element to unregister listener from. - * @param {String} type - * A string representing the - * [event type](https://developer.mozilla.org/en/DOM/event.type) to - * listen for. - * @param {Function} listener - * Function that is called whenever an event of the specified `type` - * occurs. - * @param {Boolean} capture - * If true, indicates that the user wishes to initiate capture. After - * initiating capture, all events of the specified type will be dispatched - * to the registered listener before being dispatched to any `EventTarget`s - * beneath it in the DOM tree. Events which are bubbling upward through - * the tree will not trigger a listener designated to use capture. - * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow) - * for a detailed explanation. - */ -function removeListener(element, type, listener, capture, shimmed = false) { - if (shimmed) { - element.removeEventListener(type, listener, capture); - } else { - ShimWaiver.getProperty(element, "removeEventListener")(type, listener, capture); - } -} -exports.removeListener = removeListener; - -/** - * Emits event of the specified `type` and `category` on the given `element`. - * Specified `settings` are used to initialize event before dispatching it. - * @param {Element} element - * Dom element to dispatch event on. - * @param {String} type - * A string representing the - * [event type](https://developer.mozilla.org/en/DOM/event.type). - * @param {Object} options - * Options object containing following properties: - * - `category`: String passed to the `document.createEvent`. Option is - * optional and defaults to "UIEvents". - * - `initializer`: If passed it will be used as name of the method used - * to initialize event. If omitted name will be generated from the - * `category` field by prefixing it with `"init"` and removing last - * character if it matches `"s"`. - * - `settings`: Array of settings that are forwarded to the event - * initializer after firs `type` argument. - * @see https://developer.mozilla.org/En/DOM/Document.createEvent - */ -function emit(element, type, { category, initializer, settings }, shimmed = false) { - category = category || "UIEvents"; - initializer = initializer || getInitializerName(category); - let document = element.ownerDocument; - let event = document.createEvent(category); - event[initializer].apply(event, [type].concat(settings)); - if (shimmed) { - element.dispatchEvent(event); - } else { - ShimWaiver.getProperty(element, "dispatchEvent")(event); - } -}; -exports.emit = emit; - -// Takes DOM `element` and returns promise which is resolved -// when given element is removed from it's parent node. -const removed = element => { - return new Promise(resolve => { - const { MutationObserver } = element.ownerDocument.defaultView; - const observer = new MutationObserver(mutations => { - for (let mutation of mutations) { - for (let node of mutation.removedNodes || []) { - if (node === element) { - observer.disconnect(); - resolve(element); - } - } - } - }); - observer.observe(element.parentNode, {childList: true}); - }); -}; -exports.removed = removed; - -const when = (element, eventName, capture=false, shimmed=false) => new Promise(resolve => { - const listener = event => { - if (shimmed) { - element.removeEventListener(eventName, listener, capture); - } else { - ShimWaiver.getProperty(element, "removeEventListener")(eventName, listener, capture); - } - resolve(event); - }; - - if (shimmed) { - element.addEventListener(eventName, listener, capture); - } else { - ShimWaiver.getProperty(element, "addEventListener")(eventName, listener, capture); - } -}); -exports.when = when; diff --git a/addon-sdk/source/lib/sdk/dom/events/keys.js b/addon-sdk/source/lib/sdk/dom/events/keys.js deleted file mode 100644 index e6f1483a2..000000000 --- a/addon-sdk/source/lib/sdk/dom/events/keys.js +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 { emit } = require("../events"); -const { getCodeForKey, toJSON } = require("../../keyboard/utils"); -const { has } = require("../../util/array"); -const { isString } = require("../../lang/type"); - -const INITIALIZER = "initKeyEvent"; -const CATEGORY = "KeyboardEvent"; - -function Options(options) { - if (!isString(options)) - return options; - - var { key, modifiers } = toJSON(options); - return { - key: key, - control: has(modifiers, "control"), - alt: has(modifiers, "alt"), - shift: has(modifiers, "shift"), - meta: has(modifiers, "meta") - }; -} - -var keyEvent = exports.keyEvent = function keyEvent(element, type, options) { - - emit(element, type, { - initializer: INITIALIZER, - category: CATEGORY, - settings: [ - !("bubbles" in options) || options.bubbles !== false, - !("cancelable" in options) || options.cancelable !== false, - "window" in options && options.window ? options.window : null, - "control" in options && !!options.control, - "alt" in options && !!options.alt, - "shift" in options && !!options.shift, - "meta" in options && !!options.meta, - getCodeForKey(options.key) || 0, - options.key.length === 1 ? options.key.charCodeAt(0) : 0 - ] - }); -} - -exports.keyDown = function keyDown(element, options) { - keyEvent(element, "keydown", Options(options)); -}; - -exports.keyUp = function keyUp(element, options) { - keyEvent(element, "keyup", Options(options)); -}; - -exports.keyPress = function keyPress(element, options) { - keyEvent(element, "keypress", Options(options)); -}; - diff --git a/addon-sdk/source/lib/sdk/event/chrome.js b/addon-sdk/source/lib/sdk/event/chrome.js deleted file mode 100644 index 9044fef99..000000000 --- a/addon-sdk/source/lib/sdk/event/chrome.js +++ /dev/null @@ -1,65 +0,0 @@ -/* 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 { Cc, Ci, Cr, Cu } = require("chrome"); -const { emit, on, off } = require("./core"); -var observerService = Cc["@mozilla.org/observer-service;1"] - .getService(Ci.nsIObserverService); - -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - -const { when: unload } = require("../system/unload"); - -// Simple class that can be used to instantiate event channel that -// implements `nsIObserver` interface. It's will is used by `observe` -// function as observer + event target. It basically proxies observer -// notifications as to it's registered listeners. -function ObserverChannel() {} -Object.freeze(Object.defineProperties(ObserverChannel.prototype, { - QueryInterface: { - value: function(iid) { - if (!iid.equals(Ci.nsIObserver) && - !iid.equals(Ci.nsISupportsWeakReference) && - !iid.equals(Ci.nsISupports)) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } - }, - observe: { - value: function(subject, topic, data) { - emit(this, "data", { - type: topic, - target: subject, - data: data - }); - } - } -})); - -function observe(topic) { - let observerChannel = new ObserverChannel(); - - // Note: `nsIObserverService` will not hold a weak reference to a - // observerChannel (since third argument is `true`). There for if it - // will be GC-ed with all it's event listeners once no other references - // will be held. - addObserver(observerChannel, topic, true); - - // We need to remove any observer added once the add-on is unloaded; - // otherwise we'll get a "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - unload(() => removeObserver(observerChannel, topic)); - - return observerChannel; -} - -exports.observe = observe; diff --git a/addon-sdk/source/lib/sdk/event/core.js b/addon-sdk/source/lib/sdk/event/core.js deleted file mode 100644 index c16dd2df5..000000000 --- a/addon-sdk/source/lib/sdk/event/core.js +++ /dev/null @@ -1,193 +0,0 @@ -/* 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 UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.'; -const BAD_LISTENER = 'The event listener must be a function.'; - -const { ns } = require('../core/namespace'); - -const event = ns(); - -const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/; -exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN; - -// Utility function to access given event `target` object's event listeners for -// the specific event `type`. If listeners for this type does not exists they -// will be created. -const observers = function observers(target, type) { - if (!target) throw TypeError("Event target must be an object"); - let listeners = event(target); - return type in listeners ? listeners[type] : listeners[type] = []; -}; - -/** - * Registers an event `listener` that is called every time events of - * specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function on(target, type, listener) { - if (typeof(listener) !== 'function') - throw new Error(BAD_LISTENER); - - let listeners = observers(target, type); - if (!~listeners.indexOf(listener)) - listeners.push(listener); -} -exports.on = on; - - -var onceWeakMap = new WeakMap(); - - -/** - * Registers an event `listener` that is called only the next time an event - * of the specified `type` is emitted on the given event `target`. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ -function once(target, type, listener) { - let replacement = function observer(...args) { - off(target, type, observer); - onceWeakMap.delete(listener); - listener.apply(target, args); - }; - onceWeakMap.set(listener, replacement); - on(target, type, replacement); -} -exports.once = once; - -/** - * Execute each of the listeners in order with the supplied arguments. - * All the exceptions that are thrown by listeners during the emit - * are caught and can be handled by listeners of 'error' event. Thrown - * exceptions are passed as an argument to an 'error' event listener. - * If no 'error' listener is registered exception will be logged into an - * error console. - * @param {Object} target - * Event target object. - * @param {String} type - * The type of event. - * @params {Object|Number|String|Boolean} args - * Arguments that will be passed to listeners. - */ -function emit (target, type, ...args) { - emitOnObject(target, type, target, ...args); -} -exports.emit = emit; - -/** - * A variant of emit that allows setting the this property for event listeners - */ -function emitOnObject(target, type, thisArg, ...args) { - let all = observers(target, '*').length; - let state = observers(target, type); - let listeners = state.slice(); - let count = listeners.length; - let index = 0; - - // If error event and there are no handlers (explicit or catch-all) - // then print error message to the console. - if (count === 0 && type === 'error' && all === 0) - console.exception(args[0]); - while (index < count) { - try { - let listener = listeners[index]; - // Dispatch only if listener is still registered. - if (~state.indexOf(listener)) - listener.apply(thisArg, args); - } - catch (error) { - // If exception is not thrown by a error listener and error listener is - // registered emit `error` event. Otherwise dump exception to the console. - if (type !== 'error') emit(target, 'error', error); - else console.exception(error); - } - index++; - } - // Also emit on `"*"` so that one could listen for all events. - if (type !== '*') emit(target, '*', type, ...args); -} -exports.emitOnObject = emitOnObject; - -/** - * Removes an event `listener` for the given event `type` on the given event - * `target`. If no `listener` is passed removes all listeners of the given - * `type`. If `type` is not passed removes all the listeners of the given - * event `target`. - * @param {Object} target - * The event target object. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ -function off(target, type, listener) { - let length = arguments.length; - if (length === 3) { - if (onceWeakMap.has(listener)) { - listener = onceWeakMap.get(listener); - onceWeakMap.delete(listener); - } - - let listeners = observers(target, type); - let index = listeners.indexOf(listener); - if (~index) - listeners.splice(index, 1); - } - else if (length === 2) { - observers(target, type).splice(0); - } - else if (length === 1) { - let listeners = event(target); - Object.keys(listeners).forEach(type => delete listeners[type]); - } -} -exports.off = off; - -/** - * Returns a number of event listeners registered for the given event `type` - * on the given event `target`. - */ -function count(target, type) { - return observers(target, type).length; -} -exports.count = count; - -/** - * Registers listeners on the given event `target` from the given `listeners` - * dictionary. Iterates over the listeners and if property name matches name - * pattern `onEventType` and property is a function, then registers it as - * an `eventType` listener on `target`. - * - * @param {Object} target - * The type of event. - * @param {Object} listeners - * Dictionary of listeners. - */ -function setListeners(target, listeners) { - Object.keys(listeners || {}).forEach(key => { - let match = EVENT_TYPE_PATTERN.exec(key); - let type = match && match[1].toLowerCase(); - if (!type) return; - - let listener = listeners[key]; - if (typeof(listener) === 'function') - on(target, type, listener); - }); -} -exports.setListeners = setListeners; diff --git a/addon-sdk/source/lib/sdk/event/dom.js b/addon-sdk/source/lib/sdk/event/dom.js deleted file mode 100644 index da99dec7a..000000000 --- a/addon-sdk/source/lib/sdk/event/dom.js +++ /dev/null @@ -1,78 +0,0 @@ -/* 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 { Ci } = require("chrome"); - -var { emit } = require("./core"); -var { when: unload } = require("../system/unload"); -var listeners = new WeakMap(); - -const { Cu } = require("chrome"); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {}); - -var getWindowFrom = x => - x instanceof Ci.nsIDOMWindow ? x : - x instanceof Ci.nsIDOMDocument ? x.defaultView : - x instanceof Ci.nsIDOMNode ? x.ownerDocument.defaultView : - null; - -function removeFromListeners() { - ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners); - for (let cleaner of listeners.get(this)) - cleaner(); - - listeners.delete(this); -} - -// Simple utility function takes event target, event type and optional -// `options.capture` and returns node style event stream that emits "data" -// events every time event of that type occurs on the given `target`. -function open(target, type, options) { - let output = {}; - let capture = options && options.capture ? true : false; - let listener = (event) => emit(output, "data", event); - - // `open` is currently used only on DOM Window objects, however it was made - // to be used to any kind of `target` that supports `addEventListener`, - // therefore is safer get the `window` from the `target` instead assuming - // that `target` is the `window`. - let window = getWindowFrom(target); - - // If we're not able to get a `window` from `target`, there is something - // wrong. We cannot add listeners that can leak later, or results in - // "dead object" exception. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833 - if (!window) - throw new Error("Unable to obtain the owner window from the target given."); - - let cleaners = listeners.get(window); - if (!cleaners) { - cleaners = []; - listeners.set(window, cleaners); - - // We need to remove from our map the `window` once is closed, to prevent - // memory leak - ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners); - } - - cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture)); - ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture); - - return output; -} - -unload(() => { - let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners) - for (let window of keys) - removeFromListeners.call(window); -}); - -exports.open = open; diff --git a/addon-sdk/source/lib/sdk/event/target.js b/addon-sdk/source/lib/sdk/event/target.js deleted file mode 100644 index 3a1f5e5f0..000000000 --- a/addon-sdk/source/lib/sdk/event/target.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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": "stable" -}; - -const { on, once, off, setListeners } = require('./core'); -const { method, chainable } = require('../lang/functional/core'); -const { Class } = require('../core/heritage'); - -/** - * `EventTarget` is an exemplar for creating an objects that can be used to - * add / remove event listeners on them. Events on these objects may be emitted - * via `emit` function exported by 'event/core' module. - */ -const EventTarget = Class({ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - /** - * Method initializes `this` event source. It goes through properties of a - * given `options` and registers listeners for the ones that look like an - * event listeners. - */ - initialize: function initialize(options) { - setListeners(this, options); - }, - /** - * Registers an event `listener` that is called every time events of - * specified `type` are emitted. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - * @example - * worker.on('message', function (data) { - * console.log('data received: ' + data) - * }) - */ - on: chainable(method(on)), - /** - * Registers an event `listener` that is called once the next time an event - * of the specified `type` is emitted. - * @param {String} type - * The type of the event. - * @param {Function} listener - * The listener function that processes the event. - */ - once: chainable(method(once)), - /** - * Removes an event `listener` for the given event `type`. - * @param {String} type - * The type of event. - * @param {Function} listener - * The listener function that processes the event. - */ - removeListener: function removeListener(type, listener) { - // Note: We can't just wrap `off` in `method` as we do it for other methods - // cause skipping a second or third argument will behave very differently - // than intended. This way we make sure all arguments are passed and only - // one listener is removed at most. - off(this, type, listener); - return this; - }, - // but we can wrap `off` here, as the semantics are the same - off: chainable(method(off)) - -}); -exports.EventTarget = EventTarget; diff --git a/addon-sdk/source/lib/sdk/event/utils.js b/addon-sdk/source/lib/sdk/event/utils.js deleted file mode 100644 index f193b6785..000000000 --- a/addon-sdk/source/lib/sdk/event/utils.js +++ /dev/null @@ -1,328 +0,0 @@ -/* 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" -}; - -var { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core"); -const { Cu } = require("chrome"); - -// This module provides set of high order function for working with event -// streams (streams in a NodeJS style that dispatch data, end and error -// events). - -// Function takes a `target` object and returns set of implicit references -// (non property references) it keeps. This basically allows defining -// references between objects without storing the explicitly. See transform for -// more details. -var refs = (function() { - let refSets = new WeakMap(); - return function refs(target) { - if (!refSets.has(target)) refSets.set(target, new Set()); - return refSets.get(target); - }; -})(); - -function transform(input, f) { - let output = new Output(); - - // Since event listeners don't prevent `input` to be GC-ed we wanna presrve - // it until `output` can be GC-ed. There for we add implicit reference which - // is removed once `input` ends. - refs(output).add(input); - - const next = data => receive(output, data); - once(output, "start", () => start(input)); - on(input, "error", error => emit(output, "error", error)); - on(input, "end", function() { - refs(output).delete(input); - end(output); - }); - on(input, "data", data => f(data, next)); - return output; -} - -// High order event transformation function that takes `input` event channel -// and returns transformation containing only events on which `p` predicate -// returns `true`. -function filter(input, predicate) { - return transform(input, function(data, next) { - if (predicate(data)) - next(data); - }); -} -exports.filter = filter; - -// High order function that takes `input` and returns input of it's values -// mapped via given `f` function. -const map = (input, f) => transform(input, (data, next) => next(f(data))); -exports.map = map; - -// High order function that takes `input` stream of streams and merges them -// into single event stream. Like flatten but time based rather than order -// based. -function merge(inputs) { - let output = new Output(); - let open = 1; - let state = []; - output.state = state; - refs(output).add(inputs); - - function end(input) { - open = open - 1; - refs(output).delete(input); - if (open === 0) emit(output, "end"); - } - const error = e => emit(output, "error", e); - function forward(input) { - state.push(input); - open = open + 1; - on(input, "end", () => end(input)); - on(input, "error", error); - on(input, "data", data => emit(output, "data", data)); - } - - // If `inputs` is an array treat it as a stream. - if (Array.isArray(inputs)) { - inputs.forEach(forward); - end(inputs); - } - else { - on(inputs, "end", () => end(inputs)); - on(inputs, "error", error); - on(inputs, "data", forward); - } - - return output; -} -exports.merge = merge; - -const expand = (inputs, f) => merge(map(inputs, f)); -exports.expand = expand; - -const pipe = (from, to) => on(from, "*", emit.bind(emit, to)); -exports.pipe = pipe; - - -// Shim signal APIs so other modules can be used as is. -const receive = (input, message) => { - if (input[receive]) - input[receive](input, message); - else - emit(input, "data", message); - - // Ideally our input will extend Input and already provide a weak value - // getter. If not, opportunistically shim the weak value getter on - // other types passed as the input. - if (!("value" in input)) { - Object.defineProperty(input, "value", WeakValueGetterSetter); - } - input.value = message; -}; -receive.toString = () => "@@receive"; -exports.receive = receive; -exports.send = receive; - -const end = input => { - if (input[end]) - input[end](input); - else - emit(input, "end", input); -}; -end.toString = () => "@@end"; -exports.end = end; - -const stop = input => { - if (input[stop]) - input[stop](input); - else - emit(input, "stop", input); -}; -stop.toString = () => "@@stop"; -exports.stop = stop; - -const start = input => { - if (input[start]) - input[start](input); - else - emit(input, "start", input); -}; -start.toString = () => "@@start"; -exports.start = start; - -const lift = (step, ...inputs) => { - let args = null; - let opened = inputs.length; - let started = false; - const output = new Output(); - const init = () => { - args = [...inputs.map(input => input.value)]; - output.value = step(...args); - }; - - inputs.forEach((input, index) => { - on(input, "data", data => { - args[index] = data; - receive(output, step(...args)); - }); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - init(); - }); - - init(); - - return output; -}; -exports.lift = lift; - -const merges = inputs => { - let opened = inputs.length; - let output = new Output(); - output.value = inputs[0].value; - inputs.forEach((input, index) => { - on(input, "data", data => receive(output, data)); - on(input, "end", () => { - opened = opened - 1; - if (opened <= 0) - end(output); - }); - }); - - once(output, "start", () => { - inputs.forEach(start); - output.value = inputs[0].value; - }); - - return output; -}; -exports.merges = merges; - -const foldp = (step, initial, input) => { - let output = map(input, x => step(output.value, x)); - output.value = initial; - return output; -}; -exports.foldp = foldp; - -const keepIf = (p, base, input) => { - let output = filter(input, p); - output.value = base; - return output; -}; -exports.keepIf = keepIf; - -function Input() {} -Input.start = input => emit(input, "start", input); -Input.prototype.start = Input.start; - -Input.end = input => { - emit(input, "end", input); - stop(input); -}; -Input.prototype[end] = Input.end; - -// The event channel system caches the last event seen as input.value. -// Unfortunately, if the last event is a DOM object this is a great way -// leak windows. Mitigate this by storing input.value using a weak -// reference. This allows the system to work for normal event processing -// while also allowing the objects to be reclaimed. It means, however, -// input.value cannot be accessed long after the event was dispatched. -const WeakValueGetterSetter = { - get: function() { - return this._weakValue ? this._weakValue.get() : this._simpleValue - }, - set: function(v) { - if (v && typeof v === "object") { - try { - // Try to set a weak reference. This can throw for some values. - // For example, if the value is a native object that does not - // implement nsISupportsWeakReference. - this._weakValue = Cu.getWeakReference(v) - this._simpleValue = undefined; - return; - } catch (e) { - // Do nothing. Fall through to setting _simpleValue below. - } - } - this._simpleValue = v; - this._weakValue = undefined; - }, -} -Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter); - -exports.Input = Input; - -// Define an Output type with a weak value getter for the transformation -// functions that produce new channels. -function Output() { } -Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter); -exports.Output = Output; - -const $source = "@@source"; -const $outputs = "@@outputs"; -exports.outputs = $outputs; - -// NOTE: Passing DOM objects through a Reactor can cause them to leak -// when they get cached in this.value. We cannot use a weak reference -// in this case because the Reactor design expects to always have both the -// past and present value. If we allow past values to be collected the -// system breaks. - -function Reactor(options={}) { - const {onStep, onStart, onEnd} = options; - if (onStep) - this.onStep = onStep; - if (onStart) - this.onStart = onStart; - if (onEnd) - this.onEnd = onEnd; -} -Reactor.prototype.onStep = _ => void(0); -Reactor.prototype.onStart = _ => void(0); -Reactor.prototype.onEnd = _ => void(0); -Reactor.prototype.onNext = function(present, past) { - this.value = present; - this.onStep(present, past); -}; -Reactor.prototype.run = function(input) { - on(input, "data", message => this.onNext(message, input.value)); - on(input, "end", () => this.onEnd(input.value)); - start(input); - this.value = input.value; - this.onStart(input.value); -}; -exports.Reactor = Reactor; - -/** - * Takes an object used as options with potential keys like 'onMessage', - * used to be called `require('sdk/event/core').setListeners` on. - * This strips all keys that would trigger a listener to be set. - * - * @params {Object} object - * @return {Object} - */ - -function stripListeners (object) { - return Object.keys(object || {}).reduce((agg, key) => { - if (!EVENT_TYPE_PATTERN.test(key)) - agg[key] = object[key]; - return agg; - }, {}); -} -exports.stripListeners = stripListeners; - -const when = (target, type) => new Promise(resolve => { - once(target, type, resolve); -}); -exports.when = when; diff --git a/addon-sdk/source/lib/sdk/frame/hidden-frame.js b/addon-sdk/source/lib/sdk/frame/hidden-frame.js deleted file mode 100644 index 97e0b7974..000000000 --- a/addon-sdk/source/lib/sdk/frame/hidden-frame.js +++ /dev/null @@ -1,115 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci } = require("chrome"); -const { Class } = require("../core/heritage"); -const { List, addListItem, removeListItem } = require("../util/list"); -const { EventTarget } = require("../event/target"); -const { emit } = require("../event/core"); -const { create: makeFrame } = require("./utils"); -const { defer } = require("../core/promise"); -const { when: unload } = require("../system/unload"); -const { validateOptions, getTypeOf } = require("../deprecated/api-utils"); -const { window } = require("../addon/window"); -const { fromIterator } = require("../util/array"); - -// This cache is used to access friend properties between functions -// without exposing them on the public API. -var cache = new Set(); -var elements = new WeakMap(); - -function contentLoaded(target) { - var deferred = defer(); - target.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) { - // "DOMContentLoaded" events from nested frames propagate up to target, - // ignore events unless it's DOMContentLoaded for the given target. - if (event.target === target || event.target === target.contentDocument) { - target.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); - deferred.resolve(target); - } - }, false); - return deferred.promise; -} - -function FrameOptions(options) { - options = options || {} - return validateOptions(options, FrameOptions.validator); -} -FrameOptions.validator = { - onReady: { - is: ["undefined", "function", "array"], - ok: function(v) { - if (getTypeOf(v) === "array") { - // make sure every item is a function - return v.every(item => typeof(item) === "function") - } - return true; - } - }, - onUnload: { - is: ["undefined", "function"] - } -}; - -var HiddenFrame = Class({ - extends: EventTarget, - initialize: function initialize(options) { - options = FrameOptions(options); - EventTarget.prototype.initialize.call(this, options); - }, - get element() { - return elements.get(this); - }, - toString: function toString() { - return "[object Frame]" - } -}); -exports.HiddenFrame = HiddenFrame - -function addHidenFrame(frame) { - if (!(frame instanceof HiddenFrame)) - throw Error("The object to be added must be a HiddenFrame."); - - // This instance was already added. - if (cache.has(frame)) return frame; - else cache.add(frame); - - let element = makeFrame(window.document, { - nodeName: "iframe", - type: "content", - allowJavascript: true, - allowPlugins: true, - allowAuth: true, - }); - elements.set(frame, element); - - contentLoaded(element).then(function onFrameReady(element) { - emit(frame, "ready"); - }, console.exception); - - return frame; -} -exports.add = addHidenFrame - -function removeHiddenFrame(frame) { - if (!(frame instanceof HiddenFrame)) - throw Error("The object to be removed must be a HiddenFrame."); - - if (!cache.has(frame)) return; - - // Remove from cache before calling in order to avoid loop - cache.delete(frame); - emit(frame, "unload") - let element = frame.element - if (element) element.parentNode.removeChild(element) -} -exports.remove = removeHiddenFrame; - -unload(() => fromIterator(cache).forEach(removeHiddenFrame)); diff --git a/addon-sdk/source/lib/sdk/frame/utils.js b/addon-sdk/source/lib/sdk/frame/utils.js deleted file mode 100644 index d9fccec4d..000000000 --- a/addon-sdk/source/lib/sdk/frame/utils.js +++ /dev/null @@ -1,94 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci } = require("chrome"); -const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; - -function eventTarget(frame) { - return getDocShell(frame).chromeEventHandler; -} -exports.eventTarget = eventTarget; - -function getDocShell(frame) { - let { frameLoader } = frame.QueryInterface(Ci.nsIFrameLoaderOwner); - return frameLoader && frameLoader.docShell; -} -exports.getDocShell = getDocShell; - -/** - * Creates a XUL `browser` element in a privileged document. - * @params {nsIDOMDocument} document - * @params {String} options.type - * By default is 'content' for possible values see: - * https://developer.mozilla.org/en/XUL/iframe#a-browser.type - * @params {String} options.uri - * URI of the document to be loaded into created frame. - * @params {Boolean} options.remote - * If `true` separate process will be used for this frame, also in such - * case all the following options are ignored. - * @params {Boolean} options.allowAuth - * Whether to allow auth dialogs. Defaults to `false`. - * @params {Boolean} options.allowJavascript - * Whether to allow Javascript execution. Defaults to `false`. - * @params {Boolean} options.allowPlugins - * Whether to allow plugin execution. Defaults to `false`. - */ -function create(target, options) { - target = target instanceof Ci.nsIDOMDocument ? target.documentElement : - target instanceof Ci.nsIDOMWindow ? target.document.documentElement : - target; - options = options || {}; - let remote = options.remote || false; - let namespaceURI = options.namespaceURI || XUL; - let isXUL = namespaceURI === XUL; - let nodeName = isXUL && options.browser ? 'browser' : 'iframe'; - let document = target.ownerDocument; - - let frame = document.createElementNS(namespaceURI, nodeName); - // Type="content" is mandatory to enable stuff here: - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776 - frame.setAttribute('type', options.type || 'content'); - frame.setAttribute('src', options.uri || 'about:blank'); - - // Must set the remote attribute before attaching the frame to the document - if (remote && isXUL) { - // We remove XBL binding to avoid execution of code that is not going to - // work because browser has no docShell attribute in remote mode - // (for example) - frame.setAttribute('style', '-moz-binding: none;'); - frame.setAttribute('remote', 'true'); - } - - target.appendChild(frame); - - // Load in separate process if `options.remote` is `true`. - // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347 - if (remote && !isXUL) { - frame.QueryInterface(Ci.nsIMozBrowserFrame); - frame.createRemoteFrameLoader(null); - } - - // If browser is remote it won't have a `docShell`. - if (!remote) { - let docShell = getDocShell(frame); - docShell.allowAuth = options.allowAuth || false; - docShell.allowJavascript = options.allowJavascript || false; - docShell.allowPlugins = options.allowPlugins || false; - docShell.allowWindowControl = options.allowWindowControl || false; - } - - return frame; -} -exports.create = create; - -function swapFrameLoaders(from, to) { - return from.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(to); -} -exports.swapFrameLoaders = swapFrameLoaders; diff --git a/addon-sdk/source/lib/sdk/fs/path.js b/addon-sdk/source/lib/sdk/fs/path.js deleted file mode 100644 index 4474b2b4a..000000000 --- a/addon-sdk/source/lib/sdk/fs/path.js +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -// Adapted version of: -// https://github.com/joyent/node/blob/v0.11.3/lib/path.js - -// Shim process global from node. -var process = Object.create(require('../system')); -process.cwd = process.pathFor.bind(process, 'CurProcD'); - -// Update original check in node `process.platform === 'win32'` since in SDK it's `winnt`. -var isWindows = process.platform.indexOf('win') === 0; - - - -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} - - -if (isWindows) { - // Regex to split a windows path into three parts: [*, device, slash, - // tail] windows-only - var splitDeviceRe = - /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; - - // Regex to split the tail part of the above into [*, dir, basename, ext] - var splitTailRe = - /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; - - // Function to split a filename into [root, dir, basename, ext] - // windows version - var splitPath = function(filename) { - // Separate device+slash from tail - var result = splitDeviceRe.exec(filename), - device = (result[1] || '') + (result[2] || ''), - tail = result[3] || ''; - // Split the tail into dir, basename and extension - var result2 = splitTailRe.exec(tail), - dir = result2[1], - basename = result2[2], - ext = result2[3]; - return [device, dir, basename, ext]; - }; - - var normalizeUNCRoot = function(device) { - return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\'); - }; - - // path.resolve([from ...], to) - // windows version - exports.resolve = function() { - var resolvedDevice = '', - resolvedTail = '', - resolvedAbsolute = false; - - for (var i = arguments.length - 1; i >= -1; i--) { - var path; - if (i >= 0) { - path = arguments[i]; - } else if (!resolvedDevice) { - path = process.cwd(); - } else { - // Windows has the concept of drive-specific current working - // directories. If we've resolved a drive letter but not yet an - // absolute path, get cwd for that drive. We're sure the device is not - // an unc path at this points, because unc paths are always absolute. - path = process.env['=' + resolvedDevice]; - // Verify that a drive-local cwd was found and that it actually points - // to our drive. If not, default to the drive's root. - if (!path || path.substr(0, 3).toLowerCase() !== - resolvedDevice.toLowerCase() + '\\') { - path = resolvedDevice + '\\'; - } - } - - // Skip empty and invalid entries - if (typeof path !== 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } - - var result = splitDeviceRe.exec(path), - device = result[1] || '', - isUnc = device && device.charAt(1) !== ':', - isAbsolute = exports.isAbsolute(path), - tail = result[3]; - - if (device && - resolvedDevice && - device.toLowerCase() !== resolvedDevice.toLowerCase()) { - // This path points to another device so it is not applicable - continue; - } - - if (!resolvedDevice) { - resolvedDevice = device; - } - if (!resolvedAbsolute) { - resolvedTail = tail + '\\' + resolvedTail; - resolvedAbsolute = isAbsolute; - } - - if (resolvedDevice && resolvedAbsolute) { - break; - } - } - - // Convert slashes to backslashes when `resolvedDevice` points to an UNC - // root. Also squash multiple slashes into a single one where appropriate. - if (isUnc) { - resolvedDevice = normalizeUNCRoot(resolvedDevice); - } - - // At this point the path should be resolved to a full absolute path, - // but handle relative paths to be safe (might happen when process.cwd() - // fails) - - // Normalize the tail path - - function f(p) { - return !!p; - } - - resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f), - !resolvedAbsolute).join('\\'); - - return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || - '.'; - }; - - // windows version - exports.normalize = function(path) { - var result = splitDeviceRe.exec(path), - device = result[1] || '', - isUnc = device && device.charAt(1) !== ':', - isAbsolute = exports.isAbsolute(path), - tail = result[3], - trailingSlash = /[\\\/]$/.test(tail); - - // If device is a drive letter, we'll normalize to lower case. - if (device && device.charAt(1) === ':') { - device = device[0].toLowerCase() + device.substr(1); - } - - // Normalize the tail path - tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) { - return !!p; - }), !isAbsolute).join('\\'); - - if (!tail && !isAbsolute) { - tail = '.'; - } - if (tail && trailingSlash) { - tail += '\\'; - } - - // Convert slashes to backslashes when `device` points to an UNC root. - // Also squash multiple slashes into a single one where appropriate. - if (isUnc) { - device = normalizeUNCRoot(device); - } - - return device + (isAbsolute ? '\\' : '') + tail; - }; - - // windows version - exports.isAbsolute = function(path) { - var result = splitDeviceRe.exec(path), - device = result[1] || '', - isUnc = device && device.charAt(1) !== ':'; - // UNC paths are always absolute - return !!result[2] || isUnc; - }; - - // windows version - exports.join = function() { - function f(p) { - if (typeof p !== 'string') { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; - } - - var paths = Array.prototype.filter.call(arguments, f); - var joined = paths.join('\\'); - - // Make sure that the joined path doesn't start with two slashes, because - // normalize() will mistake it for an UNC path then. - // - // This step is skipped when it is very clear that the user actually - // intended to point at an UNC path. This is assumed when the first - // non-empty string arguments starts with exactly two slashes followed by - // at least one more non-slash character. - // - // Note that for normalize() to treat a path as an UNC path it needs to - // have at least 2 components, so we don't filter for that here. - // This means that the user can use join to construct UNC paths from - // a server name and a share name; for example: - // path.join('//server', 'share') -> '\\\\server\\share\') - if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) { - joined = joined.replace(/^[\\\/]{2,}/, '\\'); - } - - return exports.normalize(joined); - }; - - // path.relative(from, to) - // it will solve the relative path from 'from' to 'to', for instance: - // from = 'C:\\orandea\\test\\aaa' - // to = 'C:\\orandea\\impl\\bbb' - // The output of the function should be: '..\\..\\impl\\bbb' - // windows version - exports.relative = function(from, to) { - from = exports.resolve(from); - to = exports.resolve(to); - - // windows is not case sensitive - var lowerFrom = from.toLowerCase(); - var lowerTo = to.toLowerCase(); - - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - - var toParts = trim(to.split('\\')); - - var lowerFromParts = trim(lowerFrom.split('\\')); - var lowerToParts = trim(lowerTo.split('\\')); - - var length = Math.min(lowerFromParts.length, lowerToParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (lowerFromParts[i] !== lowerToParts[i]) { - samePartsLength = i; - break; - } - } - - if (samePartsLength == 0) { - return to; - } - - var outputParts = []; - for (var i = samePartsLength; i < lowerFromParts.length; i++) { - outputParts.push('..'); - } - - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('\\'); - }; - - exports.sep = '\\'; - exports.delimiter = ';'; - -} else /* posix */ { - - // Split a filename into [root, dir, basename, ext], unix version - // 'root' is just a slash, or nothing. - var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; - var splitPath = function(filename) { - return splitPathRe.exec(filename).slice(1); - }; - - // path.resolve([from ...], to) - // posix version - exports.resolve = function() { - var resolvedPath = '', - resolvedAbsolute = false; - - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); - - // Skip empty and invalid entries - if (typeof path !== 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } - - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; - } - - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - - // Normalize the path - resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; - }; - - // path.normalize(path) - // posix version - exports.normalize = function(path) { - var isAbsolute = exports.isAbsolute(path), - trailingSlash = path.substr(-1) === '/'; - - // Normalize the path - path = normalizeArray(path.split('/').filter(function(p) { - return !!p; - }), !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; - }; - - // posix version - exports.isAbsolute = function(path) { - return path.charAt(0) === '/'; - }; - - // posix version - exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(paths.filter(function(p, index) { - if (typeof p !== 'string') { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; - }).join('/')); - }; - - - // path.relative(from, to) - // posix version - exports.relative = function(from, to) { - from = exports.resolve(from).substr(1); - to = exports.resolve(to).substr(1); - - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); - - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; - } - } - - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } - - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - - return outputParts.join('/'); - }; - - exports.sep = '/'; - exports.delimiter = ':'; -} - -exports.dirname = function(path) { - var result = splitPath(path), - root = result[0], - dir = result[1]; - - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } - - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } - - return root + dir; -}; - - -exports.basename = function(path, ext) { - var f = splitPath(path)[2]; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPath(path)[3]; -}; - -if (isWindows) { - exports._makeLong = function(path) { - // Note: this will *probably* throw somewhere. - if (typeof path !== 'string') - return path; - - if (!path) { - return ''; - } - - var resolvedPath = exports.resolve(path); - - if (/^[a-zA-Z]\:\\/.test(resolvedPath)) { - // path is local filesystem path, which needs to be converted - // to long UNC path. - return '\\\\?\\' + resolvedPath; - } else if (/^\\\\[^?.]/.test(resolvedPath)) { - // path is network UNC path, which needs to be converted - // to long UNC path. - return '\\\\?\\UNC\\' + resolvedPath.substring(2); - } - - return path; - }; -} else { - exports._makeLong = function(path) { - return path; - }; -}
\ No newline at end of file diff --git a/addon-sdk/source/lib/sdk/hotkeys.js b/addon-sdk/source/lib/sdk/hotkeys.js deleted file mode 100644 index 00081455e..000000000 --- a/addon-sdk/source/lib/sdk/hotkeys.js +++ /dev/null @@ -1,40 +0,0 @@ -/* 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": "stable" -}; - -const INVALID_HOTKEY = "Hotkey must have at least one modifier."; - -const { toJSON: jsonify, toString: stringify, - isFunctionKey } = require("./keyboard/utils"); -const { register, unregister } = require("./keyboard/hotkeys"); - -const Hotkey = exports.Hotkey = function Hotkey(options) { - if (!(this instanceof Hotkey)) - return new Hotkey(options); - - // Parsing key combination string. - let hotkey = jsonify(options.combo); - if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) { - throw new TypeError(INVALID_HOTKEY); - } - - this.onPress = options.onPress && options.onPress.bind(this); - this.toString = stringify.bind(null, hotkey); - // Registering listener on keyboard combination enclosed by this hotkey. - // Please note that `this.toString()` is a normalized version of - // `options.combination` where order of modifiers is sorted and `accel` is - // replaced with platform specific key. - register(this.toString(), this.onPress); - // We freeze instance before returning it in order to make it's properties - // read-only. - return Object.freeze(this); -}; -Hotkey.prototype.destroy = function destroy() { - unregister(this.toString(), this.onPress); -}; diff --git a/addon-sdk/source/lib/sdk/indexed-db.js b/addon-sdk/source/lib/sdk/indexed-db.js deleted file mode 100644 index d4d166c02..000000000 --- a/addon-sdk/source/lib/sdk/indexed-db.js +++ /dev/null @@ -1,79 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci } = require("chrome"); -const { id } = require("./self"); - -// placeholder, copied from bootstrap.js -var sanitizeId = function(id){ - let uuidRe = - /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/; - - let domain = id. - toLowerCase(). - replace(/@/g, "-at-"). - replace(/\./g, "-dot-"). - replace(uuidRe, "$1"); - - return domain -}; - -const PSEUDOURI = "indexeddb://" + sanitizeId(id) // https://bugzilla.mozilla.org/show_bug.cgi?id=779197 - -// Use XPCOM because `require("./url").URL` doesn't expose the raw uri object. -var principaluri = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService). - newURI(PSEUDOURI, null, null); - -var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"] - .getService(Ci.nsIScriptSecurityManager); -var principal = ssm.createCodebasePrincipal(principaluri, {}); - -function toArray(args) { - return Array.prototype.slice.call(args); -} - -function openInternal(args, forPrincipal, deleting) { - if (forPrincipal) { - args = toArray(args); - } else { - args = [principal].concat(toArray(args)); - } - if (args.length == 2) { - args.push({ storage: "persistent" }); - } else if (!deleting && args.length >= 3 && typeof args[2] === "number") { - args[2] = { version: args[2], storage: "persistent" }; - } - - if (deleting) { - return indexedDB.deleteForPrincipal.apply(indexedDB, args); - } - - return indexedDB.openForPrincipal.apply(indexedDB, args); -} - -exports.indexedDB = Object.freeze({ - open: function () { - return openInternal(arguments, false, false); - }, - deleteDatabase: function () { - return openInternal(arguments, false, true); - }, - openForPrincipal: function () { - return openInternal(arguments, true, false); - }, - deleteForPrincipal: function () { - return openInternal(arguments, true, true); - }, - cmp: indexedDB.cmp.bind(indexedDB) -}); - -exports.IDBKeyRange = IDBKeyRange; -exports.DOMException = Ci.nsIDOMDOMException; diff --git a/addon-sdk/source/lib/sdk/input/browser.js b/addon-sdk/source/lib/sdk/input/browser.js deleted file mode 100644 index daea875bf..000000000 --- a/addon-sdk/source/lib/sdk/input/browser.js +++ /dev/null @@ -1,73 +0,0 @@ -/* 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 { windows, isBrowser, isInteractive, isDocumentLoaded, - getOuterId } = require("../window/utils"); -const { InputPort } = require("./system"); -const { lift, merges, foldp, keepIf, start, Input } = require("../event/utils"); -const { patch } = require("diffpatcher/index"); -const { Sequence, seq, filter, object, pairs } = require("../util/sequence"); - - -// Create lazy iterators from the regular arrays, although -// once https://github.com/mozilla/addon-sdk/pull/1314 lands -// `windows` will be transforme to lazy iterators. -// When iterated over belowe sequences items will represent -// state of windows at the time of iteration. -const opened = seq(function*() { - const items = windows("navigator:browser", {includePrivate: true}); - for (let item of items) { - yield [getOuterId(item), item]; - } -}); -const interactive = filter(([_, window]) => isInteractive(window), opened); -const loaded = filter(([_, window]) => isDocumentLoaded(window), opened); - -// Helper function that converts given argument to a delta. -const Update = window => window && object([getOuterId(window), window]); -const Delete = window => window && object([getOuterId(window), null]); - - -// Signal represents delta for last top level window close. -const LastClosed = lift(Delete, - keepIf(isBrowser, null, - new InputPort({topic: "domwindowclosed"}))); -exports.LastClosed = LastClosed; - -const windowFor = document => document && document.defaultView; - -// Signal represent delta for last top level window document becoming interactive. -const InteractiveDoc = new InputPort({topic: "chrome-document-interactive"}); -const InteractiveWin = lift(windowFor, InteractiveDoc); -const LastInteractive = lift(Update, keepIf(isBrowser, null, InteractiveWin)); -exports.LastInteractive = LastInteractive; - -// Signal represent delta for last top level window loaded. -const LoadedDoc = new InputPort({topic: "chrome-document-loaded"}); -const LoadedWin = lift(windowFor, LoadedDoc); -const LastLoaded = lift(Update, keepIf(isBrowser, null, LoadedWin)); -exports.LastLoaded = LastLoaded; - - -const initialize = input => { - if (!input.initialized) { - input.value = object(...input.value); - Input.start(input); - input.initialized = true; - } -}; - -// Signal represents set of top level interactive windows, updated any -// time new window becomes interactive or one get's closed. -const Interactive = foldp(patch, interactive, merges([LastInteractive, - LastClosed])); -Interactive[start] = initialize; -exports.Interactive = Interactive; - -// Signal represents set of top level loaded window, updated any time -// new window becomes interactive or one get's closed. -const Loaded = foldp(patch, loaded, merges([LastLoaded, LastClosed])); -Loaded[start] = initialize; -exports.Loaded = Loaded; diff --git a/addon-sdk/source/lib/sdk/input/customizable-ui.js b/addon-sdk/source/lib/sdk/input/customizable-ui.js deleted file mode 100644 index a41d0971a..000000000 --- a/addon-sdk/source/lib/sdk/input/customizable-ui.js +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 { Cu } = require("chrome"); -const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); -const { receive } = require("../event/utils"); -const { InputPort } = require("./system"); -const { object} = require("../util/sequence"); -const { getOuterId } = require("../window/utils"); - -const Input = function() {}; -Input.prototype = Object.create(InputPort.prototype); - -Input.prototype.onCustomizeStart = function (window) { - receive(this, object([getOuterId(window), true])); -} - -Input.prototype.onCustomizeEnd = function (window) { - receive(this, object([getOuterId(window), null])); -} - -Input.prototype.addListener = input => CustomizableUI.addListener(input); - -Input.prototype.removeListener = input => CustomizableUI.removeListener(input); - -exports.CustomizationInput = Input; diff --git a/addon-sdk/source/lib/sdk/input/frame.js b/addon-sdk/source/lib/sdk/input/frame.js deleted file mode 100644 index 50efaa745..000000000 --- a/addon-sdk/source/lib/sdk/input/frame.js +++ /dev/null @@ -1,85 +0,0 @@ -/* 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 { InputPort } = require("./system"); -const { getFrameElement, getOuterId, - getOwnerBrowserWindow } = require("../window/utils"); -const { isnt } = require("../lang/functional"); -const { foldp, lift, merges, keepIf } = require("../event/utils"); -const { object } = require("../util/sequence"); -const { compose } = require("../lang/functional"); -const { LastClosed } = require("./browser"); -const { patch } = require("diffpatcher/index"); - -const Document = Ci.nsIDOMDocument; - -const isntNull = isnt(null); - -const frameID = frame => frame.id; -const browserID = compose(getOuterId, getOwnerBrowserWindow); - -const isInnerFrame = frame => - frame && frame.hasAttribute("data-is-sdk-inner-frame"); - -// Utility function that given content window loaded in our frame views returns -// an actual frame. This basically takes care of fact that actual frame document -// is loaded in the nested iframe. If content window is not loaded in the nested -// frame of the frame view it returs null. -const getFrame = document => - document && document.defaultView && getFrameElement(document.defaultView); - -const FrameInput = function(options) { - const input = keepIf(isInnerFrame, null, - lift(getFrame, new InputPort(options))); - return lift(frame => { - if (!frame) return frame; - const [id, owner] = [frameID(frame), browserID(frame)]; - return object([id, {owners: object([owner, options.update])}]); - }, input); -}; - -const LastLoading = new FrameInput({topic: "document-element-inserted", - update: {readyState: "loading"}}); -exports.LastLoading = LastLoading; - -const LastInteractive = new FrameInput({topic: "content-document-interactive", - update: {readyState: "interactive"}}); -exports.LastInteractive = LastInteractive; - -const LastLoaded = new FrameInput({topic: "content-document-loaded", - update: {readyState: "complete"}}); -exports.LastLoaded = LastLoaded; - -const LastUnloaded = new FrameInput({topic: "content-page-hidden", - update: null}); -exports.LastUnloaded = LastUnloaded; - -// Represents state of SDK frames in form of data structure: -// {"frame#1": {"id": "frame#1", -// "inbox": {"data": "ping", -// "target": {"id": "frame#1", "owner": "outerWindowID#2"}, -// "source": {"id": "frame#1"}} -// "url": "resource://addon-1/data/index.html", -// "owners": {"outerWindowID#1": {"readyState": "loading"}, -// "outerWindowID#2": {"readyState": "complete"}} -// -// -// frame#2: {"id": "frame#2", -// "url": "resource://addon-1/data/main.html", -// "outbox": {"data": "pong", -// "source": {"id": "frame#2", "owner": "outerWindowID#1"} -// "target": {"id": "frame#2"}} -// "owners": {outerWindowID#1: {readyState: "interacitve"}}}} -const Frames = foldp(patch, {}, merges([ - LastLoading, - LastInteractive, - LastLoaded, - LastUnloaded, - new InputPort({ id: "frame-mailbox" }), - new InputPort({ id: "frame-change" }), - new InputPort({ id: "frame-changed" }) -])); -exports.Frames = Frames; diff --git a/addon-sdk/source/lib/sdk/input/system.js b/addon-sdk/source/lib/sdk/input/system.js deleted file mode 100644 index 66bc6daec..000000000 --- a/addon-sdk/source/lib/sdk/input/system.js +++ /dev/null @@ -1,113 +0,0 @@ -/* 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, Cr, Cu } = require("chrome"); -const { Input, start, stop, end, receive, outputs } = require("../event/utils"); -const { once, off } = require("../event/core"); -const { id: addonID } = require("../self"); - -const unloadMessage = require("@loader/unload"); -const observerService = Cc['@mozilla.org/observer-service;1']. - getService(Ci.nsIObserverService); -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserver = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver"); - - -const addonUnloadTopic = "sdk:loader:destroy"; - -const isXrayWrapper = Cu.isXrayWrapper; -// In the past SDK used to double-wrap notifications dispatched, which -// made them awkward to use outside of SDK. At present they no longer -// do that, although we still supported for legacy reasons. -const isLegacyWrapper = x => - x && x.wrappedJSObject && - "observersModuleSubjectWrapper" in x.wrappedJSObject; - -const unwrapLegacy = x => x.wrappedJSObject.object; - -// `InputPort` provides a way to create a signal out of the observer -// notification subject's for the given `topic`. If `options.initial` -// is provided it is used as initial value otherwise `null` is used. -// Constructor can be given `options.id` that will be used to create -// a `topic` which is namespaced to an add-on (this avoids conflicts -// when multiple add-on are used, although in a future host probably -// should just be shared across add-ons). It is also possible to -// specify a specific `topic` via `options.topic` which is used as -// without namespacing. Created signal ends whenever add-on is -// unloaded. -const InputPort = function InputPort({id, topic, initial}) { - this.id = id || topic; - this.topic = topic || "sdk:" + addonID + ":" + id; - this.value = initial === void(0) ? null : initial; - this.observing = false; - this[outputs] = []; -}; - -// InputPort type implements `Input` signal interface. -InputPort.prototype = new Input(); -InputPort.prototype.constructor = InputPort; - -// When port is started (which is when it's subgraph get's -// first subscriber) actual observer is registered. -InputPort.start = input => { - input.addListener(input); - // Also register add-on unload observer to end this signal - // when that happens. - addObserver(input, addonUnloadTopic, false); -}; -InputPort.prototype[start] = InputPort.start; - -InputPort.addListener = input => addObserver(input, input.topic, false); -InputPort.prototype.addListener = InputPort.addListener; - -// When port is stopped (which is when it's subgraph has no -// no subcribers left) an actual observer unregistered. -// Note that port stopped once it ends as well (which is when -// add-on is unloaded). -InputPort.stop = input => { - input.removeListener(input); - removeObserver(input, addonUnloadTopic); -}; -InputPort.prototype[stop] = InputPort.stop; - -InputPort.removeListener = input => removeObserver(input, input.topic); -InputPort.prototype.removeListener = InputPort.removeListener; - -// `InputPort` also implements `nsIObserver` interface and -// `nsISupportsWeakReference` interfaces as it's going to be used as such. -InputPort.prototype.QueryInterface = function(iid) { - if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference)) - throw Cr.NS_ERROR_NO_INTERFACE; - - return this; -}; - -// `InputPort` instances implement `observe` method, which is invoked when -// observer notifications are dispatched. The `subject` of that notification -// are received on this signal. -InputPort.prototype.observe = function(subject, topic, data) { - // Unwrap message from the subject. SDK used to have it's own version of - // wrappedJSObjects which take precedence, if subject has `wrappedJSObject` - // and it's not an XrayWrapper use it as message. Otherwise use subject as - // is. - const message = subject === null ? null : - isLegacyWrapper(subject) ? unwrapLegacy(subject) : - isXrayWrapper(subject) ? subject : - subject.wrappedJSObject ? subject.wrappedJSObject : - subject; - - // If observer topic matches topic of the input port receive a message. - if (topic === this.topic) { - receive(this, message); - } - - // If observe topic is add-on unload topic we create an end message. - if (topic === addonUnloadTopic && message === unloadMessage) { - end(this); - } -}; - -exports.InputPort = InputPort; diff --git a/addon-sdk/source/lib/sdk/io/buffer.js b/addon-sdk/source/lib/sdk/io/buffer.js deleted file mode 100644 index 5ea169402..000000000 --- a/addon-sdk/source/lib/sdk/io/buffer.js +++ /dev/null @@ -1,351 +0,0 @@ -/* 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': 'experimental' -}; - -/* - * Encodings supported by TextEncoder/Decoder: - * utf-8, utf-16le, utf-16be - * http://encoding.spec.whatwg.org/#interface-textencoder - * - * Node however supports the following encodings: - * ascii, utf-8, utf-16le, usc2, base64, hex - */ - -const { Cu } = require('chrome'); -const { isNumber } = require('sdk/lang/type'); -const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {}); - -exports.TextEncoder = TextEncoder; -exports.TextDecoder = TextDecoder; - -/** - * Use WeakMaps to work around Bug 929146, which prevents us from adding - * getters or values to typed arrays - * https://bugzilla.mozilla.org/show_bug.cgi?id=929146 - */ -const parents = new WeakMap(); -const views = new WeakMap(); - -function Buffer(subject, encoding /*, bufferLength */) { - - // Allow invocation without `new` constructor - if (!(this instanceof Buffer)) - return new Buffer(subject, encoding, arguments[2]); - - var type = typeof(subject); - - switch (type) { - case 'number': - // Create typed array of the given size if number. - try { - let buffer = new Uint8Array(subject > 0 ? Math.floor(subject) : 0); - return buffer; - } catch (e) { - if (/size and count too large/.test(e.message) || - /invalid arguments/.test(e.message)) - throw new RangeError('Could not instantiate buffer: size of buffer may be too large'); - else - throw new Error('Could not instantiate buffer'); - } - break; - case 'string': - // If string encode it and use buffer for the returned Uint8Array - // to create a local patched version that acts like node buffer. - encoding = encoding || 'utf8'; - return new Uint8Array(new TextEncoder(encoding).encode(subject).buffer); - case 'object': - // This form of the constructor uses the form of - // new Uint8Array(buffer, offset, length); - // So we can instantiate a typed array within the constructor - // to inherit the appropriate properties, where both the - // `subject` and newly instantiated buffer share the same underlying - // data structure. - if (arguments.length === 3) - return new Uint8Array(subject, encoding, arguments[2]); - // If array or alike just make a copy with a local patched prototype. - else - return new Uint8Array(subject); - default: - throw new TypeError('must start with number, buffer, array or string'); - } -} -exports.Buffer = Buffer; - -// Tests if `value` is a Buffer. -Buffer.isBuffer = value => value instanceof Buffer - -// Returns true if the encoding is a valid encoding argument & false otherwise -Buffer.isEncoding = function (encoding) { - if (!encoding) return false; - try { - new TextDecoder(encoding); - } catch(e) { - return false; - } - return true; -} - -// Gives the actual byte length of a string. encoding defaults to 'utf8'. -// This is not the same as String.prototype.length since that returns the -// number of characters in a string. -Buffer.byteLength = (value, encoding = 'utf8') => - new TextEncoder(encoding).encode(value).byteLength - -// Direct copy of the nodejs's buffer implementation: -// https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177 -Buffer.concat = function(list, length) { - if (!Array.isArray(list)) - throw new TypeError('Usage: Buffer.concat(list[, length])'); - - if (typeof length === 'undefined') { - length = 0; - for (var i = 0; i < list.length; i++) - length += list[i].length; - } else { - length = ~~length; - } - - if (length < 0) - length = 0; - - if (list.length === 0) - return new Buffer(0); - else if (list.length === 1) - return list[0]; - - if (length < 0) - throw new RangeError('length is not a positive number'); - - var buffer = new Buffer(length); - var pos = 0; - for (var i = 0; i < list.length; i++) { - var buf = list[i]; - buf.copy(buffer, pos); - pos += buf.length; - } - - return buffer; -}; - -// Node buffer is very much like Uint8Array although it has bunch of methods -// that typically can be used in combination with `DataView` while preserving -// access by index. Since in SDK each module has it's own set of bult-ins it -// ok to patch ours to make it nodejs Buffer compatible. -const Uint8ArraySet = Uint8Array.prototype.set -Buffer.prototype = Uint8Array.prototype; -Object.defineProperties(Buffer.prototype, { - parent: { - get: function() { return parents.get(this, undefined); } - }, - view: { - get: function () { - let view = views.get(this, undefined); - if (view) return view; - view = new DataView(this.buffer); - views.set(this, view); - return view; - } - }, - toString: { - value: function(encoding, start, end) { - encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8'; - start = Math.max(0, ~~start); - end = Math.min(this.length, end === void(0) ? this.length : ~~end); - return new TextDecoder(encoding).decode(this.subarray(start, end)); - } - }, - toJSON: { - value: function() { - return { type: 'Buffer', data: Array.slice(this, 0) }; - } - }, - get: { - value: function(offset) { - return this[offset]; - } - }, - set: { - value: function(offset, value) { this[offset] = value; } - }, - copy: { - value: function(target, offset, start, end) { - let length = this.length; - let targetLength = target.length; - offset = isNumber(offset) ? offset : 0; - start = isNumber(start) ? start : 0; - - if (start < 0) - throw new RangeError('sourceStart is outside of valid range'); - if (end < 0) - throw new RangeError('sourceEnd is outside of valid range'); - - // If sourceStart > sourceEnd, or targetStart > targetLength, - // zero bytes copied - if (start > end || - offset > targetLength - ) - return 0; - - // If `end` is not defined, or if it is defined - // but would overflow `target`, redefine `end` - // so we can copy as much as we can - if (end - start > targetLength - offset || - end == null) { - let remainingTarget = targetLength - offset; - let remainingSource = length - start; - if (remainingSource <= remainingTarget) - end = length; - else - end = start + remainingTarget; - } - - Uint8ArraySet.call(target, this.subarray(start, end), offset); - return end - start; - } - }, - slice: { - value: function(start, end) { - let length = this.length; - start = ~~start; - end = end != null ? end : length; - - if (start < 0) { - start += length; - if (start < 0) start = 0; - } else if (start > length) - start = length; - - if (end < 0) { - end += length; - if (end < 0) end = 0; - } else if (end > length) - end = length; - - if (end < start) - end = start; - - // This instantiation uses the new Uint8Array(buffer, offset, length) version - // of construction to share the same underling data structure - let buffer = new Buffer(this.buffer, start, end - start); - - // If buffer has a value, assign its parent value to the - // buffer it shares its underlying structure with. If a slice of - // a slice, then use the root structure - if (buffer.length > 0) - parents.set(buffer, this.parent || this); - - return buffer; - } - }, - write: { - value: function(string, offset, length, encoding = 'utf8') { - // write(string, encoding); - if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) { - [offset, length, encoding] = [0, null, offset]; - } - // write(string, offset, encoding); - else if (typeof(length) === 'string') - [length, encoding] = [null, length]; - - if (offset < 0 || offset > this.length) - throw new RangeError('offset is outside of valid range'); - - offset = ~~offset; - - // Clamp length if it would overflow buffer, or if its - // undefined - if (length == null || length + offset > this.length) - length = this.length - offset; - - let buffer = new TextEncoder(encoding).encode(string); - let result = Math.min(buffer.length, length); - if (buffer.length !== length) - buffer = buffer.subarray(0, length); - - Uint8ArraySet.call(this, buffer, offset); - return result; - } - }, - fill: { - value: function fill(value, start, end) { - let length = this.length; - value = value || 0; - start = start || 0; - end = end || length; - - if (typeof(value) === 'string') - value = value.charCodeAt(0); - if (typeof(value) !== 'number' || isNaN(value)) - throw TypeError('value is not a number'); - if (end < start) - throw new RangeError('end < start'); - - // Fill 0 bytes; we're done - if (end === start) - return 0; - if (length == 0) - return 0; - - if (start < 0 || start >= length) - throw RangeError('start out of bounds'); - - if (end < 0 || end > length) - throw RangeError('end out of bounds'); - - let index = start; - while (index < end) this[index++] = value; - } - } -}); - -// Define nodejs Buffer's getter and setter functions that just proxy -// to internal DataView's equivalent methods. - -// TODO do we need to check architecture to see if it's default big/little endian? -[['readUInt16LE', 'getUint16', true], - ['readUInt16BE', 'getUint16', false], - ['readInt16LE', 'getInt16', true], - ['readInt16BE', 'getInt16', false], - ['readUInt32LE', 'getUint32', true], - ['readUInt32BE', 'getUint32', false], - ['readInt32LE', 'getInt32', true], - ['readInt32BE', 'getInt32', false], - ['readFloatLE', 'getFloat32', true], - ['readFloatBE', 'getFloat32', false], - ['readDoubleLE', 'getFloat64', true], - ['readDoubleBE', 'getFloat64', false], - ['readUInt8', 'getUint8'], - ['readInt8', 'getInt8']].forEach(([alias, name, littleEndian]) => { - Object.defineProperty(Buffer.prototype, alias, { - value: function(offset) { - return this.view[name](offset, littleEndian); - } - }); -}); - -[['writeUInt16LE', 'setUint16', true], - ['writeUInt16BE', 'setUint16', false], - ['writeInt16LE', 'setInt16', true], - ['writeInt16BE', 'setInt16', false], - ['writeUInt32LE', 'setUint32', true], - ['writeUInt32BE', 'setUint32', false], - ['writeInt32LE', 'setInt32', true], - ['writeInt32BE', 'setInt32', false], - ['writeFloatLE', 'setFloat32', true], - ['writeFloatBE', 'setFloat32', false], - ['writeDoubleLE', 'setFloat64', true], - ['writeDoubleBE', 'setFloat64', false], - ['writeUInt8', 'setUint8'], - ['writeInt8', 'setInt8']].forEach(([alias, name, littleEndian]) => { - Object.defineProperty(Buffer.prototype, alias, { - value: function(value, offset) { - return this.view[name](offset, value, littleEndian); - } - }); -}); diff --git a/addon-sdk/source/lib/sdk/io/byte-streams.js b/addon-sdk/source/lib/sdk/io/byte-streams.js deleted file mode 100644 index 6afab4369..000000000 --- a/addon-sdk/source/lib/sdk/io/byte-streams.js +++ /dev/null @@ -1,104 +0,0 @@ -/* 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": "experimental" -}; - -exports.ByteReader = ByteReader; -exports.ByteWriter = ByteWriter; - -const {Cc, Ci} = require("chrome"); - -// This just controls the maximum number of bytes we read in at one time. -const BUFFER_BYTE_LEN = 0x8000; - -function ByteReader(inputStream) { - const self = this; - - let stream = Cc["@mozilla.org/binaryinputstream;1"]. - createInstance(Ci.nsIBinaryInputStream); - stream.setInputStream(inputStream); - - let manager = new StreamManager(this, stream); - - this.read = function ByteReader_read(numBytes) { - manager.ensureOpened(); - if (typeof(numBytes) !== "number") - numBytes = Infinity; - - let data = ""; - let read = 0; - try { - while (true) { - let avail = stream.available(); - let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN); - if (toRead <= 0) - break; - data += stream.readBytes(toRead); - read += toRead; - } - } - catch (err) { - throw new Error("Error reading from stream: " + err); - } - - return data; - }; -} - -function ByteWriter(outputStream) { - const self = this; - - let stream = Cc["@mozilla.org/binaryoutputstream;1"]. - createInstance(Ci.nsIBinaryOutputStream); - stream.setOutputStream(outputStream); - - let manager = new StreamManager(this, stream); - - this.write = function ByteWriter_write(str) { - manager.ensureOpened(); - try { - stream.writeBytes(str, str.length); - } - catch (err) { - throw new Error("Error writing to stream: " + err); - } - }; -} - - -// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines -// closed and close() on stream and registers an unload listener that closes -// rawStream if it's still opened. It also provides ensureOpened(), which -// throws an exception if the stream is closed. -function StreamManager(stream, rawStream) { - const self = this; - this.rawStream = rawStream; - this.opened = true; - - stream.__defineGetter__("closed", function stream_closed() { - return !self.opened; - }); - - stream.close = function stream_close() { - self.ensureOpened(); - self.unload(); - }; - - require("../system/unload").ensure(this); -} - -StreamManager.prototype = { - ensureOpened: function StreamManager_ensureOpened() { - if (!this.opened) - throw new Error("The stream is closed and cannot be used."); - }, - unload: function StreamManager_unload() { - this.rawStream.close(); - this.opened = false; - } -}; diff --git a/addon-sdk/source/lib/sdk/io/file.js b/addon-sdk/source/lib/sdk/io/file.js deleted file mode 100644 index 47467df87..000000000 --- a/addon-sdk/source/lib/sdk/io/file.js +++ /dev/null @@ -1,196 +0,0 @@ -/* 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": "deprecated" -}; - -const {Cc,Ci,Cr} = require("chrome"); -const byteStreams = require("./byte-streams"); -const textStreams = require("./text-streams"); - -// Flags passed when opening a file. See nsprpub/pr/include/prio.h. -const OPEN_FLAGS = { - RDONLY: parseInt("0x01"), - WRONLY: parseInt("0x02"), - CREATE_FILE: parseInt("0x08"), - APPEND: parseInt("0x10"), - TRUNCATE: parseInt("0x20"), - EXCL: parseInt("0x80") -}; - -var dirsvc = Cc["@mozilla.org/file/directory_service;1"] - .getService(Ci.nsIProperties); - -function MozFile(path) { - var file = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - file.initWithPath(path); - return file; -} - -function ensureReadable(file) { - if (!file.isReadable()) - throw new Error("path is not readable: " + file.path); -} - -function ensureDir(file) { - ensureExists(file); - if (!file.isDirectory()) - throw new Error("path is not a directory: " + file.path); -} - -function ensureFile(file) { - ensureExists(file); - if (!file.isFile()) - throw new Error("path is not a file: " + file.path); -} - -function ensureExists(file) { - if (!file.exists()) - throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path); -} - -function friendlyError(errOrResult, filename) { - var isResult = typeof(errOrResult) === "number"; - var result = isResult ? errOrResult : errOrResult.result; - switch (result) { - case Cr.NS_ERROR_FILE_NOT_FOUND: - return new Error("path does not exist: " + filename); - } - return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult; -} - -exports.exists = function exists(filename) { - return MozFile(filename).exists(); -}; - -exports.isFile = function isFile(filename) { - return MozFile(filename).isFile(); -}; - -exports.read = function read(filename, mode) { - if (typeof(mode) !== "string") - mode = ""; - - // Ensure mode is read-only. - mode = /b/.test(mode) ? "b" : ""; - - var stream = exports.open(filename, mode); - try { - var str = stream.read(); - } - finally { - stream.close(); - } - - return str; -}; - -exports.join = function join(base) { - if (arguments.length < 2) - throw new Error("need at least 2 args"); - base = MozFile(base); - for (var i = 1; i < arguments.length; i++) - base.append(arguments[i]); - return base.path; -}; - -exports.dirname = function dirname(path) { - var parent = MozFile(path).parent; - return parent ? parent.path : ""; -}; - -exports.basename = function basename(path) { - var leafName = MozFile(path).leafName; - - // On Windows, leafName when the path is a volume letter and colon ("c:") is - // the path itself. But such a path has no basename, so we want the empty - // string. - return leafName == path ? "" : leafName; -}; - -exports.list = function list(path) { - var file = MozFile(path); - ensureDir(file); - ensureReadable(file); - - var entries = file.directoryEntries; - var entryNames = []; - while(entries.hasMoreElements()) { - var entry = entries.getNext(); - entry.QueryInterface(Ci.nsIFile); - entryNames.push(entry.leafName); - } - return entryNames; -}; - -exports.open = function open(filename, mode) { - var file = MozFile(filename); - if (typeof(mode) !== "string") - mode = ""; - - // File opened for write only. - if (/w/.test(mode)) { - if (file.exists()) - ensureFile(file); - var stream = Cc['@mozilla.org/network/file-output-stream;1']. - createInstance(Ci.nsIFileOutputStream); - var openFlags = OPEN_FLAGS.WRONLY | - OPEN_FLAGS.CREATE_FILE | - OPEN_FLAGS.TRUNCATE; - var permFlags = 0o644; // u+rw go+r - try { - stream.init(file, openFlags, permFlags, 0); - } - catch (err) { - throw friendlyError(err, filename); - } - return /b/.test(mode) ? - new byteStreams.ByteWriter(stream) : - new textStreams.TextWriter(stream); - } - - // File opened for read only, the default. - ensureFile(file); - stream = Cc['@mozilla.org/network/file-input-stream;1']. - createInstance(Ci.nsIFileInputStream); - try { - stream.init(file, OPEN_FLAGS.RDONLY, 0, 0); - } - catch (err) { - throw friendlyError(err, filename); - } - return /b/.test(mode) ? - new byteStreams.ByteReader(stream) : - new textStreams.TextReader(stream); -}; - -exports.remove = function remove(path) { - var file = MozFile(path); - ensureFile(file); - file.remove(false); -}; - -exports.mkpath = function mkpath(path) { - var file = MozFile(path); - if (!file.exists()) - file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); // u+rwx go+rx - else if (!file.isDirectory()) - throw new Error("The path already exists and is not a directory: " + path); -}; - -exports.rmdir = function rmdir(path) { - var file = MozFile(path); - ensureDir(file); - try { - file.remove(false); - } - catch (err) { - // Bug 566950 explains why we're not catching a specific exception here. - throw new Error("The directory is not empty: " + path); - } -}; diff --git a/addon-sdk/source/lib/sdk/io/fs.js b/addon-sdk/source/lib/sdk/io/fs.js deleted file mode 100644 index 860a884a5..000000000 --- a/addon-sdk/source/lib/sdk/io/fs.js +++ /dev/null @@ -1,984 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, CC } = require("chrome"); - -const { setTimeout } = require("../timers"); -const { Stream, InputStream, OutputStream } = require("./stream"); -const { emit, on } = require("../event/core"); -const { Buffer } = require("./buffer"); -const { ns } = require("../core/namespace"); -const { Class } = require("../core/heritage"); - - -const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", - "initWithPath"); -const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1", - "nsIFileOutputStream", "init"); -const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", - "nsIFileInputStream", "init"); -const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", "setInputStream"); -const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", - "nsIBinaryOutputStream", "setOutputStream"); -const StreamPump = CC("@mozilla.org/network/input-stream-pump;1", - "nsIInputStreamPump", "init"); - -const { createOutputTransport, createInputTransport } = - Cc["@mozilla.org/network/stream-transport-service;1"]. - getService(Ci.nsIStreamTransportService); - -const { OPEN_UNBUFFERED } = Ci.nsITransport; - - -const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream; -const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile; -const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream; - -const FILE_PERMISSION = 0o666; -const PR_UINT32_MAX = 0xfffffff; -// Values taken from: -// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615 -const PR_RDONLY = 0x01; -const PR_WRONLY = 0x02; -const PR_RDWR = 0x04; -const PR_CREATE_FILE = 0x08; -const PR_APPEND = 0x10; -const PR_TRUNCATE = 0x20; -const PR_SYNC = 0x40; -const PR_EXCL = 0x80; - -const FLAGS = { - "r": PR_RDONLY, - "r+": PR_RDWR, - "w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY, - "w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR, - "a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY, - "a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR -}; - -function accessor() { - let map = new WeakMap(); - return function(fd, value) { - if (value === null) map.delete(fd); - if (value !== undefined) map.set(fd, value); - return map.get(fd); - } -} - -var nsIFile = accessor(); -var nsIFileInputStream = accessor(); -var nsIFileOutputStream = accessor(); -var nsIBinaryInputStream = accessor(); -var nsIBinaryOutputStream = accessor(); - -// Just a contstant object used to signal that all of the file -// needs to be read. -const ALL = new String("Read all of the file"); - -function isWritable(mode) { - return !!(mode & PR_WRONLY || mode & PR_RDWR); -} -function isReadable(mode) { - return !!(mode & PR_RDONLY || mode & PR_RDWR); -} - -function isString(value) { - return typeof(value) === "string"; -} -function isFunction(value) { - return typeof(value) === "function"; -} - -function toArray(enumerator) { - let value = []; - while(enumerator.hasMoreElements()) - value.push(enumerator.getNext()) - return value -} - -function getFileName(file) { - return file.QueryInterface(Ci.nsIFile).leafName; -} - - -function remove(path, recursive) { - let fd = new nsILocalFile(path) - if (fd.exists()) { - fd.remove(recursive || false); - } - else { - throw FSError("remove", "ENOENT", 34, path); - } -} - -/** - * Utility function to convert either an octal number or string - * into an octal number - * 0777 => 0o777 - * "0644" => 0o644 - */ -function Mode(mode, fallback) { - return isString(mode) ? parseInt(mode, 8) : mode || fallback; -} -function Flags(flag) { - return !isString(flag) ? flag : - FLAGS[flag] || Error("Unknown file open flag: " + flag); -} - - -function FSError(op, code, errno, path, file, line) { - let error = Error(code + ", " + op + " " + path, file, line); - error.code = code; - error.path = path; - error.errno = errno; - return error; -} - -const ReadStream = Class({ - extends: InputStream, - initialize: function initialize(path, options) { - this.position = -1; - this.length = -1; - this.flags = "r"; - this.mode = FILE_PERMISSION; - this.bufferSize = 64 * 1024; - - options = options || {}; - - if ("flags" in options && options.flags) - this.flags = options.flags; - if ("bufferSize" in options && options.bufferSize) - this.bufferSize = options.bufferSize; - if ("length" in options && options.length) - this.length = options.length; - if ("position" in options && options.position !== undefined) - this.position = options.position; - - let { flags, mode, position, length } = this; - let fd = isString(path) ? openSync(path, flags, mode) : path; - this.fd = fd; - - let input = nsIFileInputStream(fd); - // Setting a stream position, unless it"s `-1` which means current position. - if (position >= 0) - input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); - // We use `nsIStreamTransportService` service to transform blocking - // file input stream into a fully asynchronous stream that can be written - // without blocking the main thread. - let transport = createInputTransport(input, position, length, false); - // Open an input stream on a transport. We don"t pass flags to guarantee - // non-blocking stream semantics. Also we use defaults for segment size & - // count. - InputStream.prototype.initialize.call(this, { - asyncInputStream: transport.openInputStream(null, 0, 0) - }); - - // Close file descriptor on end and destroy the stream. - on(this, "end", _ => { - this.destroy(); - emit(this, "close"); - }); - - this.read(); - }, - destroy: function() { - closeSync(this.fd); - InputStream.prototype.destroy.call(this); - } -}); -exports.ReadStream = ReadStream; -exports.createReadStream = function createReadStream(path, options) { - return new ReadStream(path, options); -}; - -const WriteStream = Class({ - extends: OutputStream, - initialize: function initialize(path, options) { - this.drainable = true; - this.flags = "w"; - this.position = -1; - this.mode = FILE_PERMISSION; - - options = options || {}; - - if ("flags" in options && options.flags) - this.flags = options.flags; - if ("mode" in options && options.mode) - this.mode = options.mode; - if ("position" in options && options.position !== undefined) - this.position = options.position; - - let { position, flags, mode } = this; - // If pass was passed we create a file descriptor out of it. Otherwise - // we just use given file descriptor. - let fd = isString(path) ? openSync(path, flags, mode) : path; - this.fd = fd; - - let output = nsIFileOutputStream(fd); - // Setting a stream position, unless it"s `-1` which means current position. - if (position >= 0) - output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); - // We use `nsIStreamTransportService` service to transform blocking - // file output stream into a fully asynchronous stream that can be written - // without blocking the main thread. - let transport = createOutputTransport(output, position, -1, false); - // Open an output stream on a transport. We don"t pass flags to guarantee - // non-blocking stream semantics. Also we use defaults for segment size & - // count. - OutputStream.prototype.initialize.call(this, { - asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0), - output: output - }); - - // For write streams "finish" basically means close. - on(this, "finish", _ => { - this.destroy(); - emit(this, "close"); - }); - }, - destroy: function() { - OutputStream.prototype.destroy.call(this); - closeSync(this.fd); - } -}); -exports.WriteStream = WriteStream; -exports.createWriteStream = function createWriteStream(path, options) { - return new WriteStream(path, options); -}; - -const Stats = Class({ - initialize: function initialize(path) { - let file = new nsILocalFile(path); - if (!file.exists()) throw FSError("stat", "ENOENT", 34, path); - nsIFile(this, file); - }, - isDirectory: function() { - return nsIFile(this).isDirectory(); - }, - isFile: function() { - return nsIFile(this).isFile(); - }, - isSymbolicLink: function() { - return nsIFile(this).isSymlink(); - }, - get mode() { - return nsIFile(this).permissions; - }, - get size() { - return nsIFile(this).fileSize; - }, - get mtime() { - return nsIFile(this).lastModifiedTime; - }, - isBlockDevice: function() { - return nsIFile(this).isSpecial(); - }, - isCharacterDevice: function() { - return nsIFile(this).isSpecial(); - }, - isFIFO: function() { - return nsIFile(this).isSpecial(); - }, - isSocket: function() { - return nsIFile(this).isSpecial(); - }, - // non standard - get exists() { - return nsIFile(this).exists(); - }, - get hidden() { - return nsIFile(this).isHidden(); - }, - get writable() { - return nsIFile(this).isWritable(); - }, - get readable() { - return nsIFile(this).isReadable(); - } -}); -exports.Stats = Stats; - -const LStats = Class({ - extends: Stats, - get size() { - return this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink : - nsIFile(this).fileSize; - }, - get mtime() { - return this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink : - nsIFile(this).lastModifiedTime; - }, - // non standard - get permissions() { - return this.isSymbolicLink() ? nsIFile(this).permissionsOfLink : - nsIFile(this).permissions; - } -}); - -const FStat = Class({ - extends: Stats, - initialize: function initialize(fd) { - nsIFile(this, nsIFile(fd)); - } -}); - -function noop() {} -function Async(wrapped) { - return function (path, callback) { - let args = Array.slice(arguments); - callback = args.pop(); - // If node is not given a callback argument - // it just does not calls it. - if (typeof(callback) !== "function") { - args.push(callback); - callback = noop; - } - setTimeout(function() { - try { - var result = wrapped.apply(this, args); - if (result === undefined) callback(null); - else callback(null, result); - } catch (error) { - callback(error); - } - }, 0); - } -} - - -/** - * Synchronous rename(2) - */ -function renameSync(oldPath, newPath) { - let source = new nsILocalFile(oldPath); - let target = new nsILocalFile(newPath); - if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath); - return source.moveTo(target.parent, target.leafName); -}; -exports.renameSync = renameSync; - -/** - * Asynchronous rename(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var rename = Async(renameSync); -exports.rename = rename; - -/** - * Test whether or not the given path exists by checking with the file system. - */ -function existsSync(path) { - return new nsILocalFile(path).exists(); -} -exports.existsSync = existsSync; - -var exists = Async(existsSync); -exports.exists = exists; - -/** - * Synchronous ftruncate(2). - */ -function truncateSync(path, length) { - let fd = openSync(path, "w"); - ftruncateSync(fd, length); - closeSync(fd); -} -exports.truncateSync = truncateSync; - -/** - * Asynchronous ftruncate(2). No arguments other than a possible exception are - * given to the completion callback. - */ -function truncate(path, length, callback) { - open(path, "w", function(error, fd) { - if (error) return callback(error); - ftruncate(fd, length, function(error) { - if (error) { - closeSync(fd); - callback(error); - } - else { - close(fd, callback); - } - }); - }); -} -exports.truncate = truncate; - -function ftruncate(fd, length, callback) { - write(fd, new Buffer(length), 0, length, 0, function(error) { - callback(error); - }); -} -exports.ftruncate = ftruncate; - -function ftruncateSync(fd, length = 0) { - writeSync(fd, new Buffer(length), 0, length, 0); -} -exports.ftruncateSync = ftruncateSync; - -function chownSync(path, uid, gid) { - throw Error("Not implemented yet!!"); -} -exports.chownSync = chownSync; - -var chown = Async(chownSync); -exports.chown = chown; - -function lchownSync(path, uid, gid) { - throw Error("Not implemented yet!!"); -} -exports.lchownSync = chownSync; - -var lchown = Async(lchown); -exports.lchown = lchown; - -/** - * Synchronous chmod(2). - */ -function chmodSync (path, mode) { - let file; - try { - file = new nsILocalFile(path); - } catch(e) { - throw FSError("chmod", "ENOENT", 34, path); - } - - file.permissions = Mode(mode); -} -exports.chmodSync = chmodSync; -/** - * Asynchronous chmod(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var chmod = Async(chmodSync); -exports.chmod = chmod; - -/** - * Synchronous chmod(2). - */ -function fchmodSync(fd, mode) { - throw Error("Not implemented yet!!"); -}; -exports.fchmodSync = fchmodSync; -/** - * Asynchronous chmod(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var fchmod = Async(fchmodSync); -exports.fchmod = fchmod; - - -/** - * Synchronous stat(2). Returns an instance of `fs.Stats` - */ -function statSync(path) { - return new Stats(path); -}; -exports.statSync = statSync; - -/** - * Asynchronous stat(2). The callback gets two arguments (err, stats) where - * stats is a `fs.Stats` object. It looks like this: - */ -var stat = Async(statSync); -exports.stat = stat; - -/** - * Synchronous lstat(2). Returns an instance of `fs.Stats`. - */ -function lstatSync(path) { - return new LStats(path); -}; -exports.lstatSync = lstatSync; - -/** - * Asynchronous lstat(2). The callback gets two arguments (err, stats) where - * stats is a fs.Stats object. lstat() is identical to stat(), except that if - * path is a symbolic link, then the link itself is stat-ed, not the file that - * it refers to. - */ -var lstat = Async(lstatSync); -exports.lstat = lstat; - -/** - * Synchronous fstat(2). Returns an instance of `fs.Stats`. - */ -function fstatSync(fd) { - return new FStat(fd); -}; -exports.fstatSync = fstatSync; - -/** - * Asynchronous fstat(2). The callback gets two arguments (err, stats) where - * stats is a fs.Stats object. - */ -var fstat = Async(fstatSync); -exports.fstat = fstat; - -/** - * Synchronous link(2). - */ -function linkSync(source, target) { - throw Error("Not implemented yet!!"); -}; -exports.linkSync = linkSync; - -/** - * Asynchronous link(2). No arguments other than a possible exception are given - * to the completion callback. - */ -var link = Async(linkSync); -exports.link = link; - -/** - * Synchronous symlink(2). - */ -function symlinkSync(source, target) { - throw Error("Not implemented yet!!"); -}; -exports.symlinkSync = symlinkSync; - -/** - * Asynchronous symlink(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var symlink = Async(symlinkSync); -exports.symlink = symlink; - -/** - * Synchronous readlink(2). Returns the resolved path. - */ -function readlinkSync(path) { - return new nsILocalFile(path).target; -}; -exports.readlinkSync = readlinkSync; - -/** - * Asynchronous readlink(2). The callback gets two arguments - * `(error, resolvedPath)`. - */ -var readlink = Async(readlinkSync); -exports.readlink = readlink; - -/** - * Synchronous realpath(2). Returns the resolved path. - */ -function realpathSync(path) { - return new nsILocalFile(path).path; -}; -exports.realpathSync = realpathSync; - -/** - * Asynchronous realpath(2). The callback gets two arguments - * `(err, resolvedPath)`. - */ -var realpath = Async(realpathSync); -exports.realpath = realpath; - -/** - * Synchronous unlink(2). - */ -var unlinkSync = remove; -exports.unlinkSync = unlinkSync; - -/** - * Asynchronous unlink(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var unlink = Async(remove); -exports.unlink = unlink; - -/** - * Synchronous rmdir(2). - */ -var rmdirSync = remove; -exports.rmdirSync = rmdirSync; - -/** - * Asynchronous rmdir(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var rmdir = Async(rmdirSync); -exports.rmdir = rmdir; - -/** - * Synchronous mkdir(2). - */ -function mkdirSync(path, mode) { - try { - return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode)); - } catch (error) { - // Adjust exception thorw to match ones thrown by node. - if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") { - let { fileName, lineNumber } = error; - error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber); - } - throw error; - } -}; -exports.mkdirSync = mkdirSync; - -/** - * Asynchronous mkdir(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var mkdir = Async(mkdirSync); -exports.mkdir = mkdir; - -/** - * Synchronous readdir(3). Returns an array of filenames excluding `"."` and - * `".."`. - */ -function readdirSync(path) { - try { - return toArray(new nsILocalFile(path).directoryEntries).map(getFileName); - } - catch (error) { - // Adjust exception thorw to match ones thrown by node. - if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" || - error.name === "NS_ERROR_FILE_NOT_FOUND") - { - let { fileName, lineNumber } = error; - error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber); - } - throw error; - } -}; -exports.readdirSync = readdirSync; - -/** - * Asynchronous readdir(3). Reads the contents of a directory. The callback - * gets two arguments `(error, files)` where `files` is an array of the names - * of the files in the directory excluding `"."` and `".."`. - */ -var readdir = Async(readdirSync); -exports.readdir = readdir; - -/** - * Synchronous close(2). - */ - function closeSync(fd) { - let input = nsIFileInputStream(fd); - let output = nsIFileOutputStream(fd); - - // Closing input stream and removing reference. - if (input) input.close(); - // Closing output stream and removing reference. - if (output) output.close(); - - nsIFile(fd, null); - nsIFileInputStream(fd, null); - nsIFileOutputStream(fd, null); - nsIBinaryInputStream(fd, null); - nsIBinaryOutputStream(fd, null); -}; -exports.closeSync = closeSync; -/** - * Asynchronous close(2). No arguments other than a possible exception are - * given to the completion callback. - */ -var close = Async(closeSync); -exports.close = close; - -/** - * Synchronous open(2). - */ -function openSync(aPath, aFlag, aMode) { - let [ fd, flags, mode, file ] = - [ { path: aPath }, Flags(aFlag), Mode(aMode), nsILocalFile(aPath) ]; - - nsIFile(fd, file); - - // If trying to open file for just read that does not exists - // need to throw exception as node does. - if (!file.exists() && !isWritable(flags)) - throw FSError("open", "ENOENT", 34, aPath); - - // If we want to open file in read mode we initialize input stream. - if (isReadable(flags)) { - let input = FileInputStream(file, flags, mode, DEFER_OPEN); - nsIFileInputStream(fd, input); - } - - // If we want to open file in write mode we initialize output stream for it. - if (isWritable(flags)) { - let output = FileOutputStream(file, flags, mode, DEFER_OPEN); - nsIFileOutputStream(fd, output); - } - - return fd; -} -exports.openSync = openSync; -/** - * Asynchronous file open. See open(2). Flags can be - * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`. - * The callback gets two arguments `(error, fd). - */ -var open = Async(openSync); -exports.open = open; - -/** - * Synchronous version of buffer-based fs.write(). Returns the number of bytes - * written. - */ -function writeSync(fd, buffer, offset, length, position) { - if (length + offset > buffer.length) { - throw Error("Length is extends beyond buffer"); - } - else if (length + offset !== buffer.length) { - buffer = buffer.slice(offset, offset + length); - } - - let output = BinaryOutputStream(nsIFileOutputStream(fd)); - nsIBinaryOutputStream(fd, output); - // We write content as a byte array as this will avoid any transcoding - // if content was a buffer. - output.writeByteArray(buffer.valueOf(), buffer.length); - output.flush(); -}; -exports.writeSync = writeSync; - -/** - * Write buffer to the file specified by fd. - * - * `offset` and `length` determine the part of the buffer to be written. - * - * `position` refers to the offset from the beginning of the file where this - * data should be written. If `position` is `null`, the data will be written - * at the current position. See pwrite(2). - * - * The callback will be given three arguments `(error, written, buffer)` where - * written specifies how many bytes were written into buffer. - * - * Note that it is unsafe to use `fs.write` multiple times on the same file - * without waiting for the callback. - */ -function write(fd, buffer, offset, length, position, callback) { - if (!Buffer.isBuffer(buffer)) { - // (fd, data, position, encoding, callback) - let encoding = null; - [ position, encoding, callback ] = Array.slice(arguments, 1); - buffer = new Buffer(String(buffer), encoding); - offset = 0; - } else if (length + offset > buffer.length) { - throw Error("Length is extends beyond buffer"); - } else if (length + offset !== buffer.length) { - buffer = buffer.slice(offset, offset + length); - } - - let writeStream = new WriteStream(fd, { position: position, - length: length }); - writeStream.on("error", callback); - writeStream.write(buffer, function onEnd() { - writeStream.destroy(); - if (callback) - callback(null, buffer.length, buffer); - }); -}; -exports.write = write; - -/** - * Synchronous version of string-based fs.read. Returns the number of - * bytes read. - */ -function readSync(fd, buffer, offset, length, position) { - let input = nsIFileInputStream(fd); - // Setting a stream position, unless it"s `-1` which means current position. - if (position >= 0) - input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); - // We use `nsIStreamTransportService` service to transform blocking - // file input stream into a fully asynchronous stream that can be written - // without blocking the main thread. - let binaryInputStream = BinaryInputStream(input); - let count = length === ALL ? binaryInputStream.available() : length; - if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer); - else { - let chunk = new Buffer(count); - binaryInputStream.readArrayBuffer(count, chunk.buffer); - chunk.copy(buffer, offset); - } - - return buffer.slice(offset, offset + count); -}; -exports.readSync = readSync; - -/** - * Read data from the file specified by `fd`. - * - * `buffer` is the buffer that the data will be written to. - * `offset` is offset within the buffer where writing will start. - * - * `length` is an integer specifying the number of bytes to read. - * - * `position` is an integer specifying where to begin reading from in the file. - * If `position` is `null`, data will be read from the current file position. - * - * The callback is given the three arguments, `(error, bytesRead, buffer)`. - */ -function read(fd, buffer, offset, length, position, callback) { - let bytesRead = 0; - let readStream = new ReadStream(fd, { position: position, length: length }); - readStream.on("data", function onData(data) { - data.copy(buffer, offset + bytesRead); - bytesRead += data.length; - }); - readStream.on("end", function onEnd() { - callback(null, bytesRead, buffer); - readStream.destroy(); - }); -}; -exports.read = read; - -/** - * Asynchronously reads the entire contents of a file. - * The callback is passed two arguments `(error, data)`, where data is the - * contents of the file. - */ -function readFile(path, encoding, callback) { - if (isFunction(encoding)) { - callback = encoding - encoding = null - } - - let buffer = null; - try { - let readStream = new ReadStream(path); - readStream.on("data", function(data) { - if (!buffer) buffer = data; - else buffer = Buffer.concat([buffer, data], 2); - }); - readStream.on("error", function onError(error) { - callback(error); - }); - readStream.on("end", function onEnd() { - // Note: Need to destroy before invoking a callback - // so that file descriptor is released. - readStream.destroy(); - callback(null, buffer); - }); - } - catch (error) { - setTimeout(callback, 0, error); - } -}; -exports.readFile = readFile; - -/** - * Synchronous version of `fs.readFile`. Returns the contents of the path. - * If encoding is specified then this function returns a string. - * Otherwise it returns a buffer. - */ -function readFileSync(path, encoding) { - let fd = openSync(path, "r"); - let size = fstatSync(fd).size; - let buffer = new Buffer(size); - try { - readSync(fd, buffer, 0, ALL, 0); - } - finally { - closeSync(fd); - } - return buffer; -}; -exports.readFileSync = readFileSync; - -/** - * Asynchronously writes data to a file, replacing the file if it already - * exists. data can be a string or a buffer. - */ -function writeFile(path, content, encoding, callback) { - if (!isString(path)) - throw new TypeError('path must be a string'); - - try { - if (isFunction(encoding)) { - callback = encoding - encoding = null - } - if (isString(content)) - content = new Buffer(content, encoding); - - let writeStream = new WriteStream(path); - let error = null; - - writeStream.end(content, function() { - writeStream.destroy(); - callback(error); - }); - - writeStream.on("error", function onError(reason) { - error = reason; - writeStream.destroy(); - }); - } catch (error) { - callback(error); - } -}; -exports.writeFile = writeFile; - -/** - * The synchronous version of `fs.writeFile`. - */ -function writeFileSync(filename, data, encoding) { - // TODO: Implement this in bug 1148209 https://bugzilla.mozilla.org/show_bug.cgi?id=1148209 - throw Error("Not implemented"); -}; -exports.writeFileSync = writeFileSync; - - -function utimesSync(path, atime, mtime) { - throw Error("Not implemented"); -} -exports.utimesSync = utimesSync; - -var utimes = Async(utimesSync); -exports.utimes = utimes; - -function futimesSync(fd, atime, mtime, callback) { - throw Error("Not implemented"); -} -exports.futimesSync = futimesSync; - -var futimes = Async(futimesSync); -exports.futimes = futimes; - -function fsyncSync(fd, atime, mtime, callback) { - throw Error("Not implemented"); -} -exports.fsyncSync = fsyncSync; - -var fsync = Async(fsyncSync); -exports.fsync = fsync; - - -/** - * Watch for changes on filename. The callback listener will be called each - * time the file is accessed. - * - * The second argument is optional. The options if provided should be an object - * containing two members a boolean, persistent, and interval, a polling value - * in milliseconds. The default is { persistent: true, interval: 0 }. - */ -function watchFile(path, options, listener) { - throw Error("Not implemented"); -}; -exports.watchFile = watchFile; - - -function unwatchFile(path, listener) { - throw Error("Not implemented"); -} -exports.unwatchFile = unwatchFile; - -function watch(path, options, listener) { - throw Error("Not implemented"); -} -exports.watch = watch; diff --git a/addon-sdk/source/lib/sdk/io/stream.js b/addon-sdk/source/lib/sdk/io/stream.js deleted file mode 100644 index 0698b8e32..000000000 --- a/addon-sdk/source/lib/sdk/io/stream.js +++ /dev/null @@ -1,440 +0,0 @@ -/* 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": "experimental" -}; - -const { CC, Cc, Ci, Cu, Cr, components } = require("chrome"); -const { EventTarget } = require("../event/target"); -const { emit } = require("../event/core"); -const { Buffer } = require("./buffer"); -const { Class } = require("../core/heritage"); -const { setTimeout } = require("../timers"); - - -const MultiplexInputStream = CC("@mozilla.org/io/multiplex-input-stream;1", - "nsIMultiplexInputStream"); -const AsyncStreamCopier = CC("@mozilla.org/network/async-stream-copier;1", - "nsIAsyncStreamCopier", "init"); -const StringInputStream = CC("@mozilla.org/io/string-input-stream;1", - "nsIStringInputStream"); -const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1", - "nsIArrayBufferInputStream"); - -const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", - "nsIBinaryInputStream", "setInputStream"); -const InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1", - "nsIInputStreamPump", "init"); - -const threadManager = Cc["@mozilla.org/thread-manager;1"]. - getService(Ci.nsIThreadManager); - -const eventTarget = Cc["@mozilla.org/network/stream-transport-service;1"]. - getService(Ci.nsIEventTarget); - -var isFunction = value => typeof(value) === "function" - -function accessor() { - let map = new WeakMap(); - return function(target, value) { - if (value) - map.set(target, value); - return map.get(target); - } -} - -const Stream = Class({ - extends: EventTarget, - initialize: function() { - this.readable = false; - this.writable = false; - this.encoding = null; - }, - setEncoding: function setEncoding(encoding) { - this.encoding = String(encoding).toUpperCase(); - }, - pipe: function pipe(target, options) { - let source = this; - function onData(chunk) { - if (target.writable) { - if (false === target.write(chunk)) - source.pause(); - } - } - function onDrain() { - if (source.readable) - source.resume(); - } - function onEnd() { - target.end(); - } - function onPause() { - source.pause(); - } - function onResume() { - if (source.readable) - source.resume(); - } - - function cleanup() { - source.removeListener("data", onData); - target.removeListener("drain", onDrain); - source.removeListener("end", onEnd); - - target.removeListener("pause", onPause); - target.removeListener("resume", onResume); - - source.removeListener("end", cleanup); - source.removeListener("close", cleanup); - - target.removeListener("end", cleanup); - target.removeListener("close", cleanup); - } - - if (!options || options.end !== false) - target.on("end", onEnd); - - source.on("data", onData); - target.on("drain", onDrain); - target.on("resume", onResume); - target.on("pause", onPause); - - source.on("end", cleanup); - source.on("close", cleanup); - - target.on("end", cleanup); - target.on("close", cleanup); - - emit(target, "pipe", source); - }, - pause: function pause() { - emit(this, "pause"); - }, - resume: function resume() { - emit(this, "resume"); - }, - destroySoon: function destroySoon() { - this.destroy(); - } -}); -exports.Stream = Stream; - - -var nsIStreamListener = accessor(); -var nsIInputStreamPump = accessor(); -var nsIAsyncInputStream = accessor(); -var nsIBinaryInputStream = accessor(); - -const StreamListener = Class({ - initialize: function(stream) { - this.stream = stream; - }, - - // Next three methods are part of `nsIStreamListener` interface and are - // invoked by `nsIInputStreamPump.asyncRead`. - onDataAvailable: function(request, context, input, offset, count) { - let stream = this.stream; - let buffer = new ArrayBuffer(count); - nsIBinaryInputStream(stream).readArrayBuffer(count, buffer); - emit(stream, "data", new Buffer(buffer)); - }, - - // Next two methods implement `nsIRequestObserver` interface and are invoked - // by `nsIInputStreamPump.asyncRead`. - onStartRequest: function() {}, - // Called to signify the end of an asynchronous request. We only care to - // discover errors. - onStopRequest: function(request, context, status) { - let stream = this.stream; - stream.readable = false; - if (!components.isSuccessCode(status)) - emit(stream, "error", status); - else - emit(stream, "end"); - } -}); - - -const InputStream = Class({ - extends: Stream, - readable: false, - paused: false, - initialize: function initialize(options) { - let { asyncInputStream } = options; - - this.readable = true; - - let binaryInputStream = new BinaryInputStream(asyncInputStream); - let inputStreamPump = new InputStreamPump(asyncInputStream, - -1, -1, 0, 0, false); - let streamListener = new StreamListener(this); - - nsIAsyncInputStream(this, asyncInputStream); - nsIInputStreamPump(this, inputStreamPump); - nsIBinaryInputStream(this, binaryInputStream); - nsIStreamListener(this, streamListener); - - this.asyncInputStream = asyncInputStream; - this.inputStreamPump = inputStreamPump; - this.binaryInputStream = binaryInputStream; - }, - get status() { - return nsIInputStreamPump(this).status; - }, - read: function() { - nsIInputStreamPump(this).asyncRead(nsIStreamListener(this), null); - }, - pause: function pause() { - this.paused = true; - nsIInputStreamPump(this).suspend(); - emit(this, "paused"); - }, - resume: function resume() { - this.paused = false; - if (nsIInputStreamPump(this).isPending()) { - nsIInputStreamPump(this).resume(); - emit(this, "resume"); - } - }, - close: function close() { - this.readable = false; - nsIInputStreamPump(this).cancel(Cr.NS_OK); - nsIBinaryInputStream(this).close(); - nsIAsyncInputStream(this).close(); - }, - destroy: function destroy() { - this.close(); - - nsIInputStreamPump(this); - nsIAsyncInputStream(this); - nsIBinaryInputStream(this); - nsIStreamListener(this); - } -}); -exports.InputStream = InputStream; - - - -var nsIRequestObserver = accessor(); -var nsIAsyncOutputStream = accessor(); -var nsIAsyncStreamCopier = accessor(); -var nsIMultiplexInputStream = accessor(); - -const RequestObserver = Class({ - initialize: function(stream) { - this.stream = stream; - }, - // Method is part of `nsIRequestObserver` interface that is - // invoked by `nsIAsyncStreamCopier.asyncCopy`. - onStartRequest: function() {}, - // Method is part of `nsIRequestObserver` interface that is - // invoked by `nsIAsyncStreamCopier.asyncCopy`. - onStopRequest: function(request, context, status) { - let stream = this.stream; - stream.drained = true; - - // Remove copied chunk. - let multiplexInputStream = nsIMultiplexInputStream(stream); - multiplexInputStream.removeStream(0); - - // If there was an error report. - if (!components.isSuccessCode(status)) - emit(stream, "error", status); - - // If there more chunks in queue then flush them. - else if (multiplexInputStream.count) - stream.flush(); - - // If stream is still writable notify that queue has drained. - else if (stream.writable) - emit(stream, "drain"); - - // If stream is no longer writable close it. - else { - nsIAsyncStreamCopier(stream).cancel(Cr.NS_OK); - nsIMultiplexInputStream(stream).close(); - nsIAsyncOutputStream(stream).close(); - nsIAsyncOutputStream(stream).flush(); - } - } -}); - -const OutputStreamCallback = Class({ - initialize: function(stream) { - this.stream = stream; - }, - // Method is part of `nsIOutputStreamCallback` interface that - // is invoked by `nsIAsyncOutputStream.asyncWait`. It is registered - // with `WAIT_CLOSURE_ONLY` flag that overrides the default behavior, - // causing the `onOutputStreamReady` notification to be suppressed until - // the stream becomes closed. - onOutputStreamReady: function(nsIAsyncOutputStream) { - emit(this.stream, "finish"); - } -}); - -const OutputStream = Class({ - extends: Stream, - writable: false, - drained: true, - get bufferSize() { - let multiplexInputStream = nsIMultiplexInputStream(this); - return multiplexInputStream && multiplexInputStream.available(); - }, - initialize: function initialize(options) { - let { asyncOutputStream, output } = options; - this.writable = true; - - // Ensure that `nsIAsyncOutputStream` was provided. - asyncOutputStream.QueryInterface(Ci.nsIAsyncOutputStream); - - // Create a `nsIMultiplexInputStream` and `nsIAsyncStreamCopier`. Former - // is used to queue written data chunks that `asyncStreamCopier` will - // asynchronously drain into `asyncOutputStream`. - let multiplexInputStream = MultiplexInputStream(); - let asyncStreamCopier = AsyncStreamCopier(multiplexInputStream, - output || asyncOutputStream, - eventTarget, - // nsIMultiplexInputStream - // implemnts .readSegments() - true, - // nsIOutputStream may or - // may not implemnet - // .writeSegments(). - false, - // Use default buffer size. - null, - // Should not close an input. - false, - // Should not close an output. - false); - - // Create `requestObserver` implementing `nsIRequestObserver` interface - // in the constructor that's gonna be reused across several flushes. - let requestObserver = RequestObserver(this); - - - // Create observer that implements `nsIOutputStreamCallback` and register - // using `WAIT_CLOSURE_ONLY` flag. That way it will be notfied once - // `nsIAsyncOutputStream` is closed. - asyncOutputStream.asyncWait(OutputStreamCallback(this), - asyncOutputStream.WAIT_CLOSURE_ONLY, - 0, - threadManager.currentThread); - - nsIRequestObserver(this, requestObserver); - nsIAsyncOutputStream(this, asyncOutputStream); - nsIMultiplexInputStream(this, multiplexInputStream); - nsIAsyncStreamCopier(this, asyncStreamCopier); - - this.asyncOutputStream = asyncOutputStream; - this.multiplexInputStream = multiplexInputStream; - this.asyncStreamCopier = asyncStreamCopier; - }, - write: function write(content, encoding, callback) { - if (isFunction(encoding)) { - callback = encoding; - encoding = callback; - } - - // If stream is not writable we throw an error. - if (!this.writable) throw Error("stream is not writable"); - - let chunk = null; - - // If content is not a buffer then we create one out of it. - if (Buffer.isBuffer(content)) { - chunk = new ArrayBufferInputStream(); - chunk.setData(content.buffer, 0, content.length); - } - else { - chunk = new StringInputStream(); - chunk.setData(content, content.length); - } - - if (callback) - this.once("drain", callback); - - // Queue up chunk to be copied to output sync. - nsIMultiplexInputStream(this).appendStream(chunk); - this.flush(); - - return this.drained; - }, - flush: function() { - if (this.drained) { - this.drained = false; - nsIAsyncStreamCopier(this).asyncCopy(nsIRequestObserver(this), null); - } - }, - end: function end(content, encoding, callback) { - if (isFunction(content)) { - callback = content - content = callback - } - if (isFunction(encoding)) { - callback = encoding - encoding = callback - } - - // Setting a listener to "finish" event if passed. - if (isFunction(callback)) - this.once("finish", callback); - - - if (content) - this.write(content, encoding); - this.writable = false; - - // Close `asyncOutputStream` only if output has drained. If it's - // not drained than `asyncStreamCopier` is busy writing, so let - // it finish. Note that since `this.writable` is false copier will - // close `asyncOutputStream` once output drains. - if (this.drained) - nsIAsyncOutputStream(this).close(); - }, - destroy: function destroy() { - nsIAsyncOutputStream(this).close(); - nsIAsyncOutputStream(this); - nsIMultiplexInputStream(this); - nsIAsyncStreamCopier(this); - nsIRequestObserver(this); - } -}); -exports.OutputStream = OutputStream; - -const DuplexStream = Class({ - extends: Stream, - implements: [InputStream, OutputStream], - allowHalfOpen: true, - initialize: function initialize(options) { - options = options || {}; - let { readable, writable, allowHalfOpen } = options; - - InputStream.prototype.initialize.call(this, options); - OutputStream.prototype.initialize.call(this, options); - - if (readable === false) - this.readable = false; - - if (writable === false) - this.writable = false; - - if (allowHalfOpen === false) - this.allowHalfOpen = false; - - // If in a half open state and it's disabled enforce end. - this.once("end", () => { - if (!this.allowHalfOpen && (!this.readable || !this.writable)) - this.end(); - }); - }, - destroy: function destroy(error) { - InputStream.prototype.destroy.call(this); - OutputStream.prototype.destroy.call(this); - } -}); -exports.DuplexStream = DuplexStream; diff --git a/addon-sdk/source/lib/sdk/io/text-streams.js b/addon-sdk/source/lib/sdk/io/text-streams.js deleted file mode 100644 index ed4ec4972..000000000 --- a/addon-sdk/source/lib/sdk/io/text-streams.js +++ /dev/null @@ -1,235 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, Cu, components } = require("chrome"); -const { ensure } = require("../system/unload"); -const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); - -// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best -// performance we use it, too. -const BUFFER_BYTE_LEN = 0x8000; -const PR_UINT32_MAX = 0xffffffff; -const DEFAULT_CHARSET = "UTF-8"; - - -/** - * An input stream that reads text from a backing stream using a given text - * encoding. - * - * @param inputStream - * The stream is backed by this nsIInputStream. It must already be - * opened. - * @param charset - * Text in inputStream is expected to be in this character encoding. If - * not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for - * documentation on how to determine other valid values for this. - */ -function TextReader(inputStream, charset) { - charset = checkCharset(charset); - - let stream = Cc["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Ci.nsIConverterInputStream); - stream.init(inputStream, charset, BUFFER_BYTE_LEN, - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - let manager = new StreamManager(this, stream); - - /** - * Reads a string from the stream. If the stream is closed, an exception is - * thrown. - * - * @param numChars - * The number of characters to read. If not given, the remainder of - * the stream is read. - * @return The string read. If the stream is already at EOS, returns the - * empty string. - */ - this.read = function TextReader_read(numChars) { - manager.ensureOpened(); - - let readAll = false; - if (typeof(numChars) === "number") - numChars = Math.max(numChars, 0); - else - readAll = true; - - let str = ""; - let totalRead = 0; - let chunkRead = 1; - - // Read in numChars or until EOS, whichever comes first. Note that the - // units here are characters, not bytes. - while (true) { - let chunk = {}; - let toRead = readAll ? - PR_UINT32_MAX : - Math.min(numChars - totalRead, PR_UINT32_MAX); - if (toRead <= 0 || chunkRead <= 0) - break; - - // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call - // to readString, enough to fill its byte buffer. chunkRead will be the - // number of characters encoded by the bytes in that buffer. - chunkRead = stream.readString(toRead, chunk); - str += chunk.value; - totalRead += chunkRead; - } - - return str; - }; -} -exports.TextReader = TextReader; - -/** - * A buffered output stream that writes text to a backing stream using a given - * text encoding. - * - * @param outputStream - * The stream is backed by this nsIOutputStream. It must already be - * opened. - * @param charset - * Text will be written to outputStream using this character encoding. - * If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl - * for documentation on how to determine other valid values for this. - */ -function TextWriter(outputStream, charset) { - charset = checkCharset(charset); - - let stream = outputStream; - - // Buffer outputStream if it's not already. - let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil); - if (!ioUtils.outputStreamIsBuffered(outputStream)) { - stream = Cc["@mozilla.org/network/buffered-output-stream;1"]. - createInstance(Ci.nsIBufferedOutputStream); - stream.init(outputStream, BUFFER_BYTE_LEN); - } - - // I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which - // we use below in writeAsync(), naturally expects its sink to be an instance - // of nsIOutputStream, which nsIConverterOutputStream's only implementation is - // not. So we use uconv and manually convert all strings before writing to - // outputStream. - let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. - createInstance(Ci.nsIScriptableUnicodeConverter); - uconv.charset = charset; - - let manager = new StreamManager(this, stream); - - /** - * Flushes the backing stream's buffer. - */ - this.flush = function TextWriter_flush() { - manager.ensureOpened(); - stream.flush(); - }; - - /** - * Writes a string to the stream. If the stream is closed, an exception is - * thrown. - * - * @param str - * The string to write. - */ - this.write = function TextWriter_write(str) { - manager.ensureOpened(); - let istream = uconv.convertToInputStream(str); - let len = istream.available(); - while (len > 0) { - stream.writeFrom(istream, len); - len = istream.available(); - } - istream.close(); - }; - - /** - * Writes a string on a background thread. After the write completes, the - * backing stream's buffer is flushed, and both the stream and the backing - * stream are closed, also on the background thread. If the stream is already - * closed, an exception is thrown immediately. - * - * @param str - * The string to write. - * @param callback - * An optional function. If given, it's called as callback(error) when - * the write completes. error is an Error object or undefined if there - * was no error. Inside callback, |this| is the stream object. - */ - this.writeAsync = function TextWriter_writeAsync(str, callback) { - manager.ensureOpened(); - let istream = uconv.convertToInputStream(str); - NetUtil.asyncCopy(istream, stream, (result) => { - let err = components.isSuccessCode(result) ? undefined : - new Error("An error occured while writing to the stream: " + result); - if (err) - console.error(err); - - // asyncCopy() closes its output (and input) stream. - manager.opened = false; - - if (typeof(callback) === "function") { - try { - callback.call(this, err); - } - catch (exc) { - console.exception(exc); - } - } - }); - }; -} -exports.TextWriter = TextWriter; - -// This manages the lifetime of stream, a TextReader or TextWriter. It defines -// closed and close() on stream and registers an unload listener that closes -// rawStream if it's still opened. It also provides ensureOpened(), which -// throws an exception if the stream is closed. -function StreamManager(stream, rawStream) { - this.rawStream = rawStream; - this.opened = true; - - /** - * True iff the stream is closed. - */ - stream.__defineGetter__("closed", () => !this.opened); - - /** - * Closes both the stream and its backing stream. If the stream is already - * closed, an exception is thrown. For TextWriters, this first flushes the - * backing stream's buffer. - */ - stream.close = () => { - this.ensureOpened(); - this.unload(); - }; - - ensure(this); -} - -StreamManager.prototype = { - ensureOpened: function StreamManager_ensureOpened() { - if (!this.opened) - throw new Error("The stream is closed and cannot be used."); - }, - unload: function StreamManager_unload() { - // TextWriter.writeAsync() causes rawStream to close and therefore sets - // opened to false, so check that we're still opened. - if (this.opened) { - // Calling close() on both an nsIUnicharInputStream and - // nsIBufferedOutputStream closes their backing streams. It also forces - // nsIOutputStreams to flush first. - this.rawStream.close(); - this.opened = false; - } - } -}; - -function checkCharset(charset) { - return typeof(charset) === "string" ? charset : DEFAULT_CHARSET; -} diff --git a/addon-sdk/source/lib/sdk/keyboard/hotkeys.js b/addon-sdk/source/lib/sdk/keyboard/hotkeys.js deleted file mode 100644 index a179502b8..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/hotkeys.js +++ /dev/null @@ -1,110 +0,0 @@ -/* 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 { observer: keyboardObserver } = require("./observer"); -const { getKeyForCode, normalize, isFunctionKey, - MODIFIERS } = require("./utils"); - -/** - * Register a global `hotkey` that executes `listener` when the key combination - * in `hotkey` is pressed. If more then one `listener` is registered on the same - * key combination only last one will be executed. - * - * @param {string} hotkey - * Key combination in the format of 'modifier key'. - * - * Examples: - * - * "accel s" - * "meta shift i" - * "control alt d" - * - * Modifier keynames: - * - * - **shift**: The Shift key. - * - **alt**: The Alt key. On the Macintosh, this is the Option key. On - * Macintosh this can only be used in conjunction with another modifier, - * since `Alt+Letter` combinations are reserved for entering special - * characters in text. - * - **meta**: The Meta key. On the Macintosh, this is the Command key. - * - **control**: The Control key. - * - **accel**: The key used for keyboard shortcuts on the user's platform, - * which is Control on Windows and Linux, and Command on Mac. Usually, this - * would be the value you would use. - * - * @param {function} listener - * Function to execute when the `hotkey` is executed. - */ -exports.register = function register(hotkey, listener) { - hotkey = normalize(hotkey); - hotkeys[hotkey] = listener; -}; - -/** - * Unregister a global `hotkey`. If passed `listener` is not the one registered - * for the given `hotkey`, the call to this function will be ignored. - * - * @param {string} hotkey - * Key combination in the format of 'modifier key'. - * @param {function} listener - * Function that will be invoked when the `hotkey` is pressed. - */ -exports.unregister = function unregister(hotkey, listener) { - hotkey = normalize(hotkey); - if (hotkeys[hotkey] === listener) - delete hotkeys[hotkey]; -}; - -/** - * Map of hotkeys and associated functions. - */ -const hotkeys = exports.hotkeys = {}; - -keyboardObserver.on("keydown", function onKeypress(event, window) { - let key, modifiers = []; - let isChar = "isChar" in event && event.isChar; - let which = "which" in event ? event.which : null; - let keyCode = "keyCode" in event ? event.keyCode : null; - - if ("shiftKey" in event && event.shiftKey) - modifiers.push("shift"); - if ("altKey" in event && event.altKey) - modifiers.push("alt"); - if ("ctrlKey" in event && event.ctrlKey) - modifiers.push("control"); - if ("metaKey" in event && event.metaKey) - modifiers.push("meta"); - - // If it's not a printable character then we fall back to a human readable - // equivalent of one of the following constants. - // http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl - key = getKeyForCode(keyCode); - - // If only non-function (f1 - f24) key or only modifiers are pressed we don't - // have a valid combination so we return immediately (Also, sometimes - // `keyCode` may be one for the modifier which means we do not have a - // modifier). - if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS) - return; - - let combination = normalize({ key: key, modifiers: modifiers }); - let hotkey = hotkeys[combination]; - - if (hotkey) { - try { - hotkey(); - } catch (exception) { - console.exception(exception); - } finally { - // Work around bug 582052 by preventing the (nonexistent) default action. - event.preventDefault(); - } - } -}); diff --git a/addon-sdk/source/lib/sdk/keyboard/observer.js b/addon-sdk/source/lib/sdk/keyboard/observer.js deleted file mode 100644 index b8e32b95c..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/observer.js +++ /dev/null @@ -1,58 +0,0 @@ -/* 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 { Class } = require("../core/heritage"); -const { EventTarget } = require("../event/target"); -const { emit } = require("../event/core"); -const { DOMEventAssembler } = require("../deprecated/events/assembler"); -const { browserWindowIterator } = require('../deprecated/window-utils'); -const { isBrowser } = require('../window/utils'); -const { observer: windowObserver } = require("../windows/observer"); - -// Event emitter objects used to register listeners and emit events on them -// when they occur. -const Observer = Class({ - implements: [DOMEventAssembler, EventTarget], - initialize() { - // Adding each opened window to a list of observed windows. - windowObserver.on("open", window => { - if (isBrowser(window)) - this.observe(window); - }); - - // Removing each closed window form the list of observed windows. - windowObserver.on("close", window => { - if (isBrowser(window)) - this.ignore(window); - }); - - // Making observer aware of already opened windows. - for (let window of browserWindowIterator()) { - this.observe(window); - } - }, - /** - * Events that are supported and emitted by the module. - */ - supportedEventsTypes: [ "keydown", "keyup", "keypress" ], - /** - * 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) { - emit(this, event.type, event, event.target.ownerDocument ? event.target.ownerDocument.defaultView - : undefined); - } -}); - -exports.observer = new Observer(); diff --git a/addon-sdk/source/lib/sdk/keyboard/utils.js b/addon-sdk/source/lib/sdk/keyboard/utils.js deleted file mode 100644 index 1b7df4ce3..000000000 --- a/addon-sdk/source/lib/sdk/keyboard/utils.js +++ /dev/null @@ -1,189 +0,0 @@ -/* 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 { Cc, Ci } = require("chrome"); -const runtime = require("../system/runtime"); -const { isString } = require("../lang/type"); -const array = require("../util/array"); - - -const SWP = "{{SEPARATOR}}"; -const SEPARATOR = "-" -const INVALID_COMBINATION = "Hotkey key combination must contain one or more " + - "modifiers and only one key"; - -// Map of modifier key mappings. -const MODIFIERS = exports.MODIFIERS = { - 'accel': runtime.OS === "Darwin" ? 'meta' : 'control', - 'meta': 'meta', - 'control': 'control', - 'ctrl': 'control', - 'option': 'alt', - 'command': 'meta', - 'alt': 'alt', - 'shift': 'shift' -}; - -// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`. -// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names. -// @See: http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl -const CODES = exports.CODES = new function Codes() { - let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent; - // Names that will be substituted with a shorter analogs. - let aliases = { - 'subtract': '-', - 'add': '+', - 'equals': '=', - 'slash': '/', - 'backslash': '\\', - 'openbracket': '[', - 'closebracket': ']', - 'quote': '\'', - 'backquote': '`', - 'period': '.', - 'semicolon': ';', - 'comma': ',' - }; - - // Normalizing keys and copying values to `this` object. - Object.keys(nsIDOMKeyEvent).filter(function(key) { - // Filter out only key codes. - return key.indexOf('DOM_VK') === 0; - }).map(function(key) { - // Map to key:values - return [ key, nsIDOMKeyEvent[key] ]; - }).map(function([key, value]) { - return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ]; - }).forEach(function ([ key, value ]) { - this[aliases[key] || key] = value; - }, this); -}; - -// Inverted `CODES` hash of `code:key`. -const KEYS = exports.KEYS = new function Keys() { - Object.keys(CODES).forEach(function(key) { - this[CODES[key]] = key; - }, this) -} - -exports.getKeyForCode = function getKeyForCode(code) { - return (code in KEYS) && KEYS[code]; -}; -exports.getCodeForKey = function getCodeForKey(key) { - return (key in CODES) && CODES[key]; -}; - -/** - * Utility function that takes string or JSON that defines a `hotkey` and - * returns normalized string version of it. - * @param {JSON|String} hotkey - * @param {String} [separator=" "] - * Optional string that represents separator used to concatenate keys in the - * given `hotkey`. - * @returns {String} - * @examples - * - * require("keyboard/hotkeys").normalize("b Shift accel"); - * // 'control shift b' -> on windows & linux - * // 'meta shift b' -> on mac - * require("keyboard/hotkeys").normalize("alt-d-shift", "-"); - * // 'alt shift d' - */ -var normalize = exports.normalize = function normalize(hotkey, separator) { - if (!isString(hotkey)) - hotkey = toString(hotkey, separator); - return toString(toJSON(hotkey, separator), separator); -}; - -/* - * Utility function that splits a string of characters that defines a `hotkey` - * into modifier keys and the defining key. - * @param {String} hotkey - * @param {String} [separator=" "] - * Optional string that represents separator used to concatenate keys in the - * given `hotkey`. - * @returns {JSON} - * @examples - * - * require("keyboard/hotkeys").toJSON("accel shift b"); - * // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux - * // { key: 'b', modifiers: [ 'meta', 'shift' ] } -> on mac - * - * require("keyboard/hotkeys").normalize("alt-d-shift", "-"); - * // { key: 'd', modifiers: [ 'alt', 'shift' ] } - */ -var toJSON = exports.toJSON = function toJSON(hotkey, separator) { - separator = separator || SEPARATOR; - // Since default separator is `-`, combination may take form of `alt--`. To - // avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where - // `{{SEPARATOR}}` can be swapped later. - hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP); - - let value = {}; - let modifiers = []; - let keys = hotkey.split(separator); - keys.forEach(function(name) { - // If name is `SEPARATOR` than we swap it back. - if (name === SWP) - name = separator; - if (name in MODIFIERS) { - array.add(modifiers, MODIFIERS[name]); - } else { - if (!value.key) - value.key = name; - else - throw new TypeError(INVALID_COMBINATION); - } - }); - - if (!value.key) - throw new TypeError(INVALID_COMBINATION); - - value.modifiers = modifiers.sort(); - return value; -}; - -/** - * Utility function that takes object that defines a `hotkey` and returns - * string representation of it. - * - * _Please note that this function does not validates data neither it normalizes - * it, if you are unsure that data is well formed use `normalize` function - * instead. - * - * @param {JSON} hotkey - * @param {String} [separator=" "] - * Optional string that represents separator used to concatenate keys in the - * given `hotkey`. - * @returns {String} - * @examples - * - * require("keyboard/hotkeys").toString({ - * key: 'b', - * modifiers: [ 'control', 'shift' ] - * }, '+'); - * // 'control+shift+b - * - */ -var toString = exports.toString = function toString(hotkey, separator) { - let keys = hotkey.modifiers.slice(); - keys.push(hotkey.key); - return keys.join(separator || SEPARATOR); -}; - -/** - * Utility function takes `key` name and returns `true` if it's function key - * (F1, ..., F24) and `false` if it's not. - */ -var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) { - var $ - return key[0].toLowerCase() === 'f' && - ($ = parseInt(key.substr(1)), 0 < $ && $ < 25); -}; diff --git a/addon-sdk/source/lib/sdk/l10n.js b/addon-sdk/source/lib/sdk/l10n.js deleted file mode 100644 index db5a9d7b6..000000000 --- a/addon-sdk/source/lib/sdk/l10n.js +++ /dev/null @@ -1,91 +0,0 @@ -/* 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": "stable" -}; - -const json = require("./l10n/json/core"); -const { get: getKey } = require("./l10n/core"); -const properties = require("./l10n/properties/core"); -const { getRulesForLocale } = require("./l10n/plural-rules"); - -// Retrieve the plural mapping function -var pluralMappingFunction = getRulesForLocale(json.language()) || - getRulesForLocale("en"); - -exports.get = function get(k) { - // For now, we only accept a "string" as first argument - // TODO: handle plural forms in gettext pattern - if (typeof k !== "string") - throw new Error("First argument of localization method should be a string"); - let n = arguments[1]; - - // Get translation from big hashmap or default to hard coded string: - let localized = getKey(k, n) || k; - - // # Simplest usecase: - // // String hard coded in source code: - // _("Hello world") - // // Identifier of a key stored in properties file - // _("helloString") - if (arguments.length <= 1) - return localized; - - let args = Array.slice(arguments); - let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)]; - - if (typeof localized == "object" && "other" in localized) { - // # Plural form: - // // Strings hard coded in source code: - // _(["One download", "%d downloads"], 10); - // // Identifier of a key stored in properties file - // _("downloadNumber", 0); - let n = arguments[1]; - - // First handle simple universal forms that may not be mandatory - // for each language, (i.e. not different than 'other' form, - // but still usefull for better phrasing) - // For example 0 in english is the same form than 'other' - // but we accept 'zero' form if specified in localization file - if (n === 0 && "zero" in localized) - localized = localized["zero"]; - else if (n === 1 && "one" in localized) - localized = localized["one"]; - else if (n === 2 && "two" in localized) - localized = localized["two"]; - else { - let pluralForm = pluralMappingFunction(n); - if (pluralForm in localized) - localized = localized[pluralForm]; - else // Fallback in case of error: missing plural form - localized = localized["other"]; - } - - // Simulate a string with one placeholder: - args = [null, n]; - } - - // # String with placeholders: - // // Strings hard coded in source code: - // _("Hello %s", username) - // // Identifier of a key stored in properties file - // _("helloString", username) - // * We supports `%1s`, `%2s`, ... pattern in order to change arguments order - // in translation. - // * In case of plural form, we has `%d` instead of `%s`. - let offset = 1; - if (placeholders.length > 1) { - args = placeholders; - } - - localized = localized.replace(/%(\d*)[sd]/g, (v, n) => { - let rv = args[n != "" ? n : offset]; - offset++; - return rv; - }); - - return localized; -} diff --git a/addon-sdk/source/lib/sdk/l10n/core.js b/addon-sdk/source/lib/sdk/l10n/core.js deleted file mode 100644 index 2f8f84c04..000000000 --- a/addon-sdk/source/lib/sdk/l10n/core.js +++ /dev/null @@ -1,9 +0,0 @@ -/* 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 json = require("./json/core"); -const properties = require("./properties/core"); - -exports.get = json.usingJSON ? json.get : properties.get; diff --git a/addon-sdk/source/lib/sdk/l10n/html.js b/addon-sdk/source/lib/sdk/l10n/html.js deleted file mode 100644 index fa2cf9cf0..000000000 --- a/addon-sdk/source/lib/sdk/l10n/html.js +++ /dev/null @@ -1,32 +0,0 @@ -/* 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 { processes, remoteRequire } = require("../remote/parent"); -remoteRequire("sdk/content/l10n-html"); - -var enabled = false; -function enable() { - if (!enabled) { - processes.port.emit("sdk/l10n/html/enable"); - enabled = true; - } -} -exports.enable = enable; - -function disable() { - if (enabled) { - processes.port.emit("sdk/l10n/html/disable"); - enabled = false; - } -} -exports.disable = disable; - -processes.forEvery(process => { - process.port.emit(enabled ? "sdk/l10n/html/enable" : "sdk/l10n/html/disable"); -}); diff --git a/addon-sdk/source/lib/sdk/l10n/json/core.js b/addon-sdk/source/lib/sdk/l10n/json/core.js deleted file mode 100644 index af52f956f..000000000 --- a/addon-sdk/source/lib/sdk/l10n/json/core.js +++ /dev/null @@ -1,36 +0,0 @@ -/* 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" -}; - -var usingJSON = false; -var hash = {}, bestMatchingLocale = null; -try { - let data = require("@l10n/data"); - hash = data.hash; - bestMatchingLocale = data.bestMatchingLocale; - usingJSON = true; -} -catch(e) {} - -exports.usingJSON = usingJSON; - -// Returns the translation for a given key, if available. -exports.get = function get(k) { - return k in hash ? hash[k] : null; -} - -// Returns the full length locale code: ja-JP-mac, en-US or fr -exports.locale = function locale() { - return bestMatchingLocale; -} - -// Returns the short locale code: ja, en, fr -exports.language = function language() { - return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase() - : "en"; -} diff --git a/addon-sdk/source/lib/sdk/l10n/loader.js b/addon-sdk/source/lib/sdk/l10n/loader.js deleted file mode 100644 index 60e219e44..000000000 --- a/addon-sdk/source/lib/sdk/l10n/loader.js +++ /dev/null @@ -1,70 +0,0 @@ -/* 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 { Cc, Ci } = require("chrome"); -const { getPreferedLocales, findClosestLocale } = require("./locale"); -const { readURI } = require("../net/url"); -const { resolve } = require("../core/promise"); - -function parseJsonURI(uri) { - return readURI(uri). - then(JSON.parse). - then(null, function (error) { - throw Error("Failed to parse locale file:\n" + uri + "\n" + error); - }); -} - -// Returns the array stored in `locales.json` manifest that list available -// locales files -function getAvailableLocales(rootURI) { - let uri = rootURI + "locales.json"; - return parseJsonURI(uri).then(function (manifest) { - return "locales" in manifest && - Array.isArray(manifest.locales) ? - manifest.locales : []; - }); -} - -// Returns URI of the best locales file to use from the XPI -function getBestLocale(rootURI) { - // Read localization manifest file that contains list of available languages - return getAvailableLocales(rootURI).then(function (availableLocales) { - // Retrieve list of prefered locales to use - let preferedLocales = getPreferedLocales(); - - // Compute the most preferable locale to use by using these two lists - return findClosestLocale(availableLocales, preferedLocales); - }); -} - -/** - * Read localization files and returns a promise of data to put in `@l10n/data` - * pseudo module, in order to allow l10n/json/core to fetch it. - */ -exports.load = function load(rootURI) { - // First, search for a locale file: - return getBestLocale(rootURI).then(function (bestMatchingLocale) { - // It may be null if the addon doesn't have any locale file - if (!bestMatchingLocale) - return resolve(null); - - let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json"; - - // Locale files only contains one big JSON object that is used as - // an hashtable of: "key to translate" => "translated key" - // TODO: We are likely to change this in order to be able to overload - // a specific key translation. For a specific package, module or line? - return parseJsonURI(localeURI).then(function (json) { - return { - hash: json, - bestMatchingLocale: bestMatchingLocale - }; - }); - }); -} diff --git a/addon-sdk/source/lib/sdk/l10n/locale.js b/addon-sdk/source/lib/sdk/l10n/locale.js deleted file mode 100644 index 950b33b20..000000000 --- a/addon-sdk/source/lib/sdk/l10n/locale.js +++ /dev/null @@ -1,127 +0,0 @@ -/* 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 prefs = require("../preferences/service"); -const { Cu, Cc, Ci } = require("chrome"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm"); - -/** - * Gets the currently selected locale for display. - * Gets all usable locale that we can use sorted by priority of relevance - * @return Array of locales, begins with highest priority - */ -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; -const PREF_ACCEPT_LANGUAGES = "intl.accept_languages"; - -function getPreferedLocales(caseSensitve) { - let locales = []; - function addLocale(locale) { - locale = locale.trim(); - if (!caseSensitve) - locale = locale.toLowerCase(); - if (locales.indexOf(locale) === -1) - locales.push(locale); - } - - // Most important locale is OS one. But we use it, only if - // "intl.locale.matchOS" pref is set to `true`. - // Currently only used for multi-locales mobile builds. - // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46 - if (prefs.get(PREF_MATCH_OS_LOCALE, false)) { - let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"]. - getService(Ci.nsILocaleService); - let osLocale = localeService.getLocaleComponentForUserAgent(); - addLocale(osLocale); - } - - // In some cases, mainly on Fennec and on Linux version, - // `general.useragent.locale` is a special 'localized' value, like: - // "chrome://global/locale/intl.properties" - let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") || - prefs.get(PREF_SELECTED_LOCALE, ""); - if (browserUiLocale) - addLocale(browserUiLocale); - - // Third priority is the list of locales used for web content - let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") || - prefs.get(PREF_ACCEPT_LANGUAGES, ""); - if (contentLocales) { - // This list is a string of locales seperated by commas. - // There is spaces after commas, so strip each item - for (let locale of contentLocales.split(",")) - addLocale(locale.replace(/(^\s+)|(\s+$)/g, "")); - } - - // Finally, we ensure that en-US is the final fallback if it wasn't added - addLocale("en-US"); - - return locales; -} -exports.getPreferedLocales = getPreferedLocales; - -/** - * Selects the closest matching locale from a list of locales. - * - * @param aLocales - * An array of available locales - * @param aMatchLocales - * An array of prefered locales, ordered by priority. Most wanted first. - * Locales have to be in lowercase. - * If null, uses getPreferedLocales() results - * @return the best match for the currently selected locale - * - * Stolen from http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm - */ -exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) { - aMatchLocales = aMatchLocales || getPreferedLocales(); - - // Holds the best matching localized resource - let bestmatch = null; - // The number of locale parts it matched with - let bestmatchcount = 0; - // The number of locale parts in the match - let bestpartcount = 0; - - for (let locale of aMatchLocales) { - let lparts = locale.split("-"); - for (let localized of aLocales) { - let found = localized.toLowerCase(); - // Exact match is returned immediately - if (locale == found) - return localized; - - let fparts = found.split("-"); - /* If we have found a possible match and this one isn't any longer - then we dont need to check further. */ - if (bestmatch && fparts.length < bestmatchcount) - continue; - - // Count the number of parts that match - let maxmatchcount = Math.min(fparts.length, lparts.length); - let matchcount = 0; - while (matchcount < maxmatchcount && - fparts[matchcount] == lparts[matchcount]) - matchcount++; - - /* If we matched more than the last best match or matched the same and - this locale is less specific than the last best match. */ - if (matchcount > bestmatchcount || - (matchcount == bestmatchcount && fparts.length < bestpartcount)) { - bestmatch = localized; - bestmatchcount = matchcount; - bestpartcount = fparts.length; - } - } - // If we found a valid match for this locale return it - if (bestmatch) - return bestmatch; - } - return null; -} diff --git a/addon-sdk/source/lib/sdk/l10n/plural-rules.js b/addon-sdk/source/lib/sdk/l10n/plural-rules.js deleted file mode 100644 index a3ef48a5e..000000000 --- a/addon-sdk/source/lib/sdk/l10n/plural-rules.js +++ /dev/null @@ -1,407 +0,0 @@ -/* 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/. */ - -// This file is automatically generated with /python-lib/plural-rules-generator.py -// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml - -// Mapping of short locale name == to == > rule index in following list - -module.metadata = { - "stability": "unstable" -}; - -const LOCALES_TO_RULES = { - "af": 3, - "ak": 4, - "am": 4, - "ar": 1, - "asa": 3, - "az": 0, - "be": 11, - "bem": 3, - "bez": 3, - "bg": 3, - "bh": 4, - "bm": 0, - "bn": 3, - "bo": 0, - "br": 20, - "brx": 3, - "bs": 11, - "ca": 3, - "cgg": 3, - "chr": 3, - "cs": 12, - "cy": 17, - "da": 3, - "de": 3, - "dv": 3, - "dz": 0, - "ee": 3, - "el": 3, - "en": 3, - "eo": 3, - "es": 3, - "et": 3, - "eu": 3, - "fa": 0, - "ff": 5, - "fi": 3, - "fil": 4, - "fo": 3, - "fr": 5, - "fur": 3, - "fy": 3, - "ga": 8, - "gd": 24, - "gl": 3, - "gsw": 3, - "gu": 3, - "guw": 4, - "gv": 23, - "ha": 3, - "haw": 3, - "he": 2, - "hi": 4, - "hr": 11, - "hu": 0, - "id": 0, - "ig": 0, - "ii": 0, - "is": 3, - "it": 3, - "iu": 7, - "ja": 0, - "jmc": 3, - "jv": 0, - "ka": 0, - "kab": 5, - "kaj": 3, - "kcg": 3, - "kde": 0, - "kea": 0, - "kk": 3, - "kl": 3, - "km": 0, - "kn": 0, - "ko": 0, - "ksb": 3, - "ksh": 21, - "ku": 3, - "kw": 7, - "lag": 18, - "lb": 3, - "lg": 3, - "ln": 4, - "lo": 0, - "lt": 10, - "lv": 6, - "mas": 3, - "mg": 4, - "mk": 16, - "ml": 3, - "mn": 3, - "mo": 9, - "mr": 3, - "ms": 0, - "mt": 15, - "my": 0, - "nah": 3, - "naq": 7, - "nb": 3, - "nd": 3, - "ne": 3, - "nl": 3, - "nn": 3, - "no": 3, - "nr": 3, - "nso": 4, - "ny": 3, - "nyn": 3, - "om": 3, - "or": 3, - "pa": 3, - "pap": 3, - "pl": 13, - "ps": 3, - "pt": 3, - "rm": 3, - "ro": 9, - "rof": 3, - "ru": 11, - "rwk": 3, - "sah": 0, - "saq": 3, - "se": 7, - "seh": 3, - "ses": 0, - "sg": 0, - "sh": 11, - "shi": 19, - "sk": 12, - "sl": 14, - "sma": 7, - "smi": 7, - "smj": 7, - "smn": 7, - "sms": 7, - "sn": 3, - "so": 3, - "sq": 3, - "sr": 11, - "ss": 3, - "ssy": 3, - "st": 3, - "sv": 3, - "sw": 3, - "syr": 3, - "ta": 3, - "te": 3, - "teo": 3, - "th": 0, - "ti": 4, - "tig": 3, - "tk": 3, - "tl": 4, - "tn": 3, - "to": 0, - "tr": 0, - "ts": 3, - "tzm": 22, - "uk": 11, - "ur": 3, - "ve": 3, - "vi": 0, - "vun": 3, - "wa": 4, - "wae": 3, - "wo": 0, - "xh": 3, - "xog": 3, - "yo": 0, - "zh": 0, - "zu": 3 -}; - -// Utility functions for plural rules methods -function isIn(n, list) { - return list.indexOf(n) !== -1; -} -function isBetween(n, start, end) { - return start <= n && n <= end; -} - -// List of all plural rules methods, that maps an integer to the plural form name to use -const RULES = { - "0": function (n) { - - return "other" - }, - "1": function (n) { - if ((isBetween((n % 100), 3, 10))) - return "few"; - if (n == 0) - return "zero"; - if ((isBetween((n % 100), 11, 99))) - return "many"; - if (n == 2) - return "two"; - if (n == 1) - return "one"; - return "other" - }, - "2": function (n) { - if (n != 0 && (n % 10) == 0) - return "many"; - if (n == 2) - return "two"; - if (n == 1) - return "one"; - return "other" - }, - "3": function (n) { - if (n == 1) - return "one"; - return "other" - }, - "4": function (n) { - if ((isBetween(n, 0, 1))) - return "one"; - return "other" - }, - "5": function (n) { - if ((isBetween(n, 0, 2)) && n != 2) - return "one"; - return "other" - }, - "6": function (n) { - if (n == 0) - return "zero"; - if ((n % 10) == 1 && (n % 100) != 11) - return "one"; - return "other" - }, - "7": function (n) { - if (n == 2) - return "two"; - if (n == 1) - return "one"; - return "other" - }, - "8": function (n) { - if ((isBetween(n, 3, 6))) - return "few"; - if ((isBetween(n, 7, 10))) - return "many"; - if (n == 2) - return "two"; - if (n == 1) - return "one"; - return "other" - }, - "9": function (n) { - if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19))) - return "few"; - if (n == 1) - return "one"; - return "other" - }, - "10": function (n) { - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) - return "few"; - if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) - return "one"; - return "other" - }, - "11": function (n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return "few"; - if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14))) - return "many"; - if ((n % 10) == 1 && (n % 100) != 11) - return "one"; - return "other" - }, - "12": function (n) { - if ((isBetween(n, 2, 4))) - return "few"; - if (n == 1) - return "one"; - return "other" - }, - "13": function (n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return "few"; - if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14))) - return "many"; - if (n == 1) - return "one"; - return "other" - }, - "14": function (n) { - if ((isBetween((n % 100), 3, 4))) - return "few"; - if ((n % 100) == 2) - return "two"; - if ((n % 100) == 1) - return "one"; - return "other" - }, - "15": function (n) { - if (n == 0 || (isBetween((n % 100), 2, 10))) - return "few"; - if ((isBetween((n % 100), 11, 19))) - return "many"; - if (n == 1) - return "one"; - return "other" - }, - "16": function (n) { - if ((n % 10) == 1 && n != 11) - return "one"; - return "other" - }, - "17": function (n) { - if (n == 3) - return "few"; - if (n == 0) - return "zero"; - if (n == 6) - return "many"; - if (n == 2) - return "two"; - if (n == 1) - return "one"; - return "other" - }, - "18": function (n) { - if (n == 0) - return "zero"; - if ((isBetween(n, 0, 2)) && n != 0 && n != 2) - return "one"; - return "other" - }, - "19": function (n) { - if ((isBetween(n, 2, 10))) - return "few"; - if ((isBetween(n, 0, 1))) - return "one"; - return "other" - }, - "20": function (n) { - if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99))) - return "few"; - if ((n % 1000000) == 0 && n != 0) - return "many"; - if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) - return "two"; - if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) - return "one"; - return "other" - }, - "21": function (n) { - if (n == 0) - return "zero"; - if (n == 1) - return "one"; - return "other" - }, - "22": function (n) { - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) - return "one"; - return "other" - }, - "23": function (n) { - if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0) - return "one"; - return "other" - }, - "24": function (n) { - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) - return "few"; - if (isIn(n, [2, 12])) - return "two"; - if (isIn(n, [1, 11])) - return "one"; - return "other" - }, -}; - -/** - * Return a function that gives the plural form name for a given integer - * for the specified `locale` - * let fun = getRulesForLocale('en'); - * fun(1) -> 'one' - * fun(0) -> 'other' - * fun(1000) -> 'other' - */ -exports.getRulesForLocale = function getRulesForLocale(locale) { - let index = LOCALES_TO_RULES[locale]; - if (!(index in RULES)) { - console.warn('Plural form unknown for locale "' + locale + '"'); - return function () { return "other"; }; - } - return RULES[index]; -} - diff --git a/addon-sdk/source/lib/sdk/l10n/prefs.js b/addon-sdk/source/lib/sdk/l10n/prefs.js deleted file mode 100644 index 8ee26fc5b..000000000 --- a/addon-sdk/source/lib/sdk/l10n/prefs.js +++ /dev/null @@ -1,51 +0,0 @@ -/* 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 } = require("../system/events"); -const core = require("./core"); -const { id: jetpackId } = require('../self'); - -const OPTIONS_DISPLAYED = "addon-options-displayed"; - -function enable() { - on(OPTIONS_DISPLAYED, onOptionsDisplayed); -} -exports.enable = enable; - -function onOptionsDisplayed({ subject: document, data: addonId }) { - if (addonId !== jetpackId) - return; - localizeInlineOptions(document); -} - -function localizeInlineOptions(document) { - let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' + - 'button[data-jetpack-id="' + jetpackId + '"][pref-name]'; - let nodes = document.querySelectorAll(query); - for (let node of nodes) { - let name = node.getAttribute("pref-name"); - if (node.tagName == "setting") { - let desc = core.get(name + "_description"); - if (desc) - node.setAttribute("desc", desc); - let title = core.get(name + "_title"); - if (title) - node.setAttribute("title", title); - - for (let item of node.querySelectorAll("menuitem, radio")) { - let key = name + "_options." + item.getAttribute("label"); - let label = core.get(key); - if (label) - item.setAttribute("label", label); - } - } - else if (node.tagName == "button") { - let label = core.get(name + "_label"); - if (label) - node.setAttribute("label", label); - } - } -} -exports.localizeInlineOptions = localizeInlineOptions; diff --git a/addon-sdk/source/lib/sdk/l10n/properties/core.js b/addon-sdk/source/lib/sdk/l10n/properties/core.js deleted file mode 100644 index 7a9081d0b..000000000 --- a/addon-sdk/source/lib/sdk/l10n/properties/core.js +++ /dev/null @@ -1,87 +0,0 @@ -/* 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 { Cu } = require("chrome"); -const { newURI } = require('../../url/utils') -const { getRulesForLocale } = require("../plural-rules"); -const { getPreferedLocales } = require('../locale'); -const { rootURI } = require("@loader/options"); -const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -const baseURI = rootURI + "locale/"; -const preferedLocales = getPreferedLocales(true); - -// Make sure we don't get stale data after an update -// (See Bug 1300735 for rationale). -Services.strings.flushBundles(); - -function getLocaleURL(locale) { - // if the locale is a valid chrome URI, return it - try { - let uri = newURI(locale); - if (uri.scheme == 'chrome') - return uri.spec; - } - catch(_) {} - // otherwise try to construct the url - return baseURI + locale + ".properties"; -} - -function getKey(locale, key) { - let bundle = Services.strings.createBundle(getLocaleURL(locale)); - try { - return bundle.GetStringFromName(key) + ""; - } - catch (_) {} - return undefined; -} - -function get(key, n, locales) { - // try this locale - let locale = locales.shift(); - let localized; - - if (typeof n == 'number') { - if (n == 0) { - localized = getKey(locale, key + '[zero]'); - } - else if (n == 1) { - localized = getKey(locale, key + '[one]'); - } - else if (n == 2) { - localized = getKey(locale, key + '[two]'); - } - - if (!localized) { - // Retrieve the plural mapping function - let pluralForm = (getRulesForLocale(locale.split("-")[0].toLowerCase()) || - getRulesForLocale("en"))(n); - localized = getKey(locale, key + '[' + pluralForm + ']'); - } - - if (!localized) { - localized = getKey(locale, key + '[other]'); - } - } - - if (!localized) { - localized = getKey(locale, key); - } - - if (!localized) { - localized = getKey(locale, key + '[other]'); - } - - if (localized) { - return localized; - } - - // try next locale - if (locales.length) - return get(key, n, locales); - - return undefined; -} -exports.get = (k, n) => get(k, n, Array.slice(preferedLocales)); diff --git a/addon-sdk/source/lib/sdk/lang/functional.js b/addon-sdk/source/lib/sdk/lang/functional.js deleted file mode 100644 index 66e30edfa..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional.js +++ /dev/null @@ -1,47 +0,0 @@ -/* 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/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { defer, remit, delay, debounce, - throttle } = require("./functional/concurrent"); -const { method, invoke, partial, curry, compose, wrap, identity, memoize, once, - cache, complement, constant, when, apply, flip, field, query, - isInstance, chainable, is, isnt } = require("./functional/core"); - -exports.defer = defer; -exports.remit = remit; -exports.delay = delay; -exports.debounce = debounce; -exports.throttle = throttle; - -exports.method = method; -exports.invoke = invoke; -exports.partial = partial; -exports.curry = curry; -exports.compose = compose; -exports.wrap = wrap; -exports.identity = identity; -exports.memoize = memoize; -exports.once = once; -exports.cache = cache; -exports.complement = complement; -exports.constant = constant; -exports.when = when; -exports.apply = apply; -exports.flip = flip; -exports.field = field; -exports.query = query; -exports.isInstance = isInstance; -exports.chainable = chainable; -exports.is = is; -exports.isnt = isnt; diff --git a/addon-sdk/source/lib/sdk/lang/functional/concurrent.js b/addon-sdk/source/lib/sdk/lang/functional/concurrent.js deleted file mode 100644 index 85e8cff46..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/concurrent.js +++ /dev/null @@ -1,110 +0,0 @@ -/* 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/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -}; - -const { arity, name, derive, invoke } = require("./helpers"); -const { setTimeout, clearTimeout, setImmediate } = require("../../timers"); - -/** - * Takes a function and returns a wrapped one instead, calling which will call - * original function in the next turn of event loop. This is basically utility - * to do `setImmediate(function() { ... })`, with a difference that returned - * function is reused, instead of creating a new one each time. This also allows - * to use this functions as event listeners. - */ -const defer = f => derive(function(...args) { - setImmediate(invoke, f, args, this); -}, f); -exports.defer = defer; -// Exporting `remit` alias as `defer` may conflict with promises. -exports.remit = defer; - -/** - * Much like setTimeout, invokes function after wait milliseconds. If you pass - * the optional arguments, they will be forwarded on to the function when it is - * invoked. - */ -const delay = function delay(f, ms, ...args) { - setTimeout(() => f.apply(this, args), ms); -}; -exports.delay = delay; - -/** - * From underscore's `_.debounce` - * http://underscorejs.org - * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Underscore may be freely distributed under the MIT license. - */ -const debounce = function debounce (fn, wait) { - let timeout, args, context, timestamp, result; - - let later = function () { - let last = Date.now() - timestamp; - if (last < wait) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - result = fn.apply(context, args); - context = args = null; - } - }; - - return function (...aArgs) { - context = this; - args = aArgs; - timestamp = Date.now(); - if (!timeout) { - timeout = setTimeout(later, wait); - } - - return result; - }; -}; -exports.debounce = debounce; - -/** - * From underscore's `_.throttle` - * http://underscorejs.org - * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Underscore may be freely distributed under the MIT license. - */ -const throttle = function throttle (func, wait, options) { - let context, args, result; - let timeout = null; - let previous = 0; - options || (options = {}); - let later = function() { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - context = args = null; - }; - return function() { - let now = Date.now(); - if (!previous && options.leading === false) previous = now; - let remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; -}; -exports.throttle = throttle; diff --git a/addon-sdk/source/lib/sdk/lang/functional/core.js b/addon-sdk/source/lib/sdk/lang/functional/core.js deleted file mode 100644 index 0d9143364..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/core.js +++ /dev/null @@ -1,290 +0,0 @@ -/* 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/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -} -const { arity, name, derive, invoke } = require("./helpers"); - -/** - * Takes variadic numeber of functions and returns composed one. - * Returned function pushes `this` pseudo-variable to the head - * of the passed arguments and invokes all the functions from - * left to right passing same arguments to them. Composite function - * returns return value of the right most funciton. - */ -const method = (...lambdas) => { - return function method(...args) { - args.unshift(this); - return lambdas.reduce((_, lambda) => lambda.apply(this, args), - void(0)); - }; -}; -exports.method = method; - -/** - * Invokes `callee` by passing `params` as an arguments and `self` as `this` - * pseudo-variable. Returns value that is returned by a callee. - * @param {Function} callee - * Function to invoke. - * @param {Array} params - * Arguments to invoke function with. - * @param {Object} self - * Object to be passed as a `this` pseudo variable. - */ -exports.invoke = invoke; - -/** - * Takes a function and bind values to one or more arguments, returning a new - * function of smaller arity. - * - * @param {Function} fn - * The function to partial - * - * @returns The new function with binded values - */ -const partial = (f, ...curried) => { - if (typeof(f) !== "function") - throw new TypeError(String(f) + " is not a function"); - - let fn = derive(function(...args) { - return f.apply(this, curried.concat(args)); - }, f); - fn.arity = arity(f) - curried.length; - return fn; -}; -exports.partial = partial; - -/** - * Returns function with implicit currying, which will continue currying until - * expected number of argument is collected. Expected number of arguments is - * determined by `fn.length`. Using this with variadic functions is stupid, - * so don't do it. - * - * @examples - * - * var sum = curry(function(a, b) { - * return a + b - * }) - * console.log(sum(2, 2)) // 4 - * console.log(sum(2)(4)) // 6 - */ -const curry = new function() { - const currier = (fn, arity, params) => { - // Function either continues to curry arguments or executes function - // if desired arguments have being collected. - const curried = function(...input) { - // Prepend all curried arguments to the given arguments. - if (params) input.unshift.apply(input, params); - // If expected number of arguments has being collected invoke fn, - // othrewise return curried version Otherwise continue curried. - return (input.length >= arity) ? fn.apply(this, input) : - currier(fn, arity, input); - }; - curried.arity = arity - (params ? params.length : 0); - - return curried; - }; - - return fn => currier(fn, arity(fn)); -}; -exports.curry = curry; - -/** - * Returns the composition of a list of functions, where each function consumes - * the return value of the function that follows. In math terms, composing the - * functions `f()`, `g()`, and `h()` produces `f(g(h()))`. - * @example - * - * var greet = function(name) { return "hi: " + name; }; - * var exclaim = function(statement) { return statement + "!"; }; - * var welcome = compose(exclaim, greet); - * - * welcome('moe'); // => 'hi: moe!' - */ -function compose(...lambdas) { - return function composed(...args) { - let index = lambdas.length; - while (0 <= --index) - args = [lambdas[index].apply(this, args)]; - - return args[0]; - }; -} -exports.compose = compose; - -/* - * Returns the first function passed as an argument to the second, - * allowing you to adjust arguments, run code before and after, and - * conditionally execute the original function. - * @example - * - * var hello = function(name) { return "hello: " + name; }; - * hello = wrap(hello, function(f) { - * return "before, " + f("moe") + ", after"; - * }); - * - * hello(); // => 'before, hello: moe, after' - */ -const wrap = (f, wrapper) => derive(function wrapped(...args) { - return wrapper.apply(this, [f].concat(args)); -}, f); -exports.wrap = wrap; - -/** - * Returns the same value that is used as the argument. In math: f(x) = x - */ -const identity = value => value; -exports.identity = identity; - -/** - * Memoizes a given function by caching the computed result. Useful for - * speeding up slow-running computations. If passed an optional hashFunction, - * it will be used to compute the hash key for storing the result, based on - * the arguments to the original function. The default hashFunction just uses - * the first argument to the memoized function as the key. - */ -const memoize = (f, hasher) => { - let memo = Object.create(null); - let cache = new WeakMap(); - hasher = hasher || identity; - return derive(function memoizer(...args) { - const key = hasher.apply(this, args); - const type = typeof(key); - if (key && (type === "object" || type === "function")) { - if (!cache.has(key)) - cache.set(key, f.apply(this, args)); - return cache.get(key); - } - else { - if (!(key in memo)) - memo[key] = f.apply(this, args); - return memo[key]; - } - }, f); -}; -exports.memoize = memoize; - -/* - * Creates a version of the function that can only be called one time. Repeated - * calls to the modified function will have no effect, returning the value from - * the original call. Useful for initialization functions, instead of having to - * set a boolean flag and then check it later. - */ -const once = f => { - let ran = false, cache; - return derive(function(...args) { - return ran ? cache : (ran = true, cache = f.apply(this, args)); - }, f); -}; -exports.once = once; -// export cache as once will may be conflicting with event once a lot. -exports.cache = once; - -// Takes a `f` function and returns a function that takes the same -// arguments as `f`, has the same effects, if any, and returns the -// opposite truth value. -const complement = f => derive(function(...args) { - return args.length < arity(f) ? complement(partial(f, ...args)) : - !f.apply(this, args); -}, f); -exports.complement = complement; - -// Constructs function that returns `x` no matter what is it -// invoked with. -const constant = x => _ => x; -exports.constant = constant; - -// Takes `p` predicate, `consequent` function and an optional -// `alternate` function and composes function that returns -// application of arguments over `consequent` if application over -// `p` is `true` otherwise returns application over `alternate`. -// If `alternate` is not a function returns `undefined`. -const when = (p, consequent, alternate) => { - if (typeof(alternate) !== "function" && alternate !== void(0)) - throw TypeError("alternate must be a function"); - if (typeof(consequent) !== "function") - throw TypeError("consequent must be a function"); - - return function(...args) { - return p.apply(this, args) ? - consequent.apply(this, args) : - alternate && alternate.apply(this, args); - }; -}; -exports.when = when; - -// Apply function that behaves as `apply` does in lisp: -// apply(f, x, [y, z]) => f.apply(f, [x, y, z]) -// apply(f, x) => f.apply(f, [x]) -const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop())); -exports.apply = apply; - -// Returns function identical to given `f` but with flipped order -// of arguments. -const flip = f => derive(function(...args) { - return f.apply(this, args.reverse()); -}, f); -exports.flip = flip; - -// Takes field `name` and `target` and returns value of that field. -// If `target` is `null` or `undefined` it would be returned back -// instead of attempt to access it's field. Function is implicitly -// curried, this allows accessor function generation by calling it -// with only `name` argument. -const field = curry((name, target) => - // Note: Permisive `==` is intentional. - target == null ? target : target[name]); -exports.field = field; - -// Takes `.` delimited string representing `path` to a nested field -// and a `target` to get it from. For convinience function is -// implicitly curried, there for accessors can be created by invoking -// it with just a `path` argument. -const query = curry((path, target) => { - const names = path.split("."); - const count = names.length; - let index = 0; - let result = target; - // Note: Permisive `!=` is intentional. - while (result != null && index < count) { - result = result[names[index]]; - index = index + 1; - } - return result; -}); -exports.query = query; - -// Takes `Type` (constructor function) and a `value` and returns -// `true` if `value` is instance of the given `Type`. Function is -// implicitly curried this allows predicate generation by calling -// function with just first argument. -const isInstance = curry((Type, value) => value instanceof Type); -exports.isInstance = isInstance; - -/* - * Takes a funtion and returns a wrapped function that returns `this` - */ -const chainable = f => derive(function(...args) { - f.apply(this, args); - return this; -}, f); -exports.chainable = chainable; - -// Functions takes `expected` and `actual` values and returns `true` if -// `expected === actual`. Returns curried function if called with less then -// two arguments. -// -// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ] -const is = curry((expected, actual) => actual === expected); -exports.is = is; - -const isnt = complement(is); -exports.isnt = isnt; diff --git a/addon-sdk/source/lib/sdk/lang/functional/helpers.js b/addon-sdk/source/lib/sdk/lang/functional/helpers.js deleted file mode 100644 index 60f4e3300..000000000 --- a/addon-sdk/source/lib/sdk/lang/functional/helpers.js +++ /dev/null @@ -1,29 +0,0 @@ -/* 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/. */ - -// Disclaimer: Some of the functions in this module implement APIs from -// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for -// those goes to him. - -"use strict"; - -module.metadata = { - "stability": "unstable" -} - -const arity = f => f.arity || f.length; -exports.arity = arity; - -const name = f => f.displayName || f.name; -exports.name = name; - -const derive = (f, source) => { - f.displayName = name(source); - f.arity = arity(source); - return f; -}; -exports.derive = derive; - -const invoke = (callee, params, self) => callee.apply(self, params); -exports.invoke = invoke; diff --git a/addon-sdk/source/lib/sdk/lang/type.js b/addon-sdk/source/lib/sdk/lang/type.js deleted file mode 100644 index b50e6be4c..000000000 --- a/addon-sdk/source/lib/sdk/lang/type.js +++ /dev/null @@ -1,388 +0,0 @@ -/* 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" -}; - -/** - * Returns `true` if `value` is `undefined`. - * @examples - * var foo; isUndefined(foo); // true - * isUndefined(0); // false - */ -function isUndefined(value) { - return value === undefined; -} -exports.isUndefined = isUndefined; - -/** - * Returns `true` if value is `null`. - * @examples - * isNull(null); // true - * isNull(undefined); // false - */ -function isNull(value) { - return value === null; -} -exports.isNull = isNull; - -/** - * Returns `true` if value is `null` or `undefined`. - * It's equivalent to `== null`, but resolve the ambiguity of the writer - * intention, makes clear that he's clearly checking both `null` and `undefined` - * values, and it's not a typo for `=== null`. - */ -function isNil(value) { - return value === null || value === undefined; -} -exports.isNil = isNil; - -function isBoolean(value) { - return typeof value === "boolean"; -} -exports.isBoolean = isBoolean; -/** - * Returns `true` if value is a string. - * @examples - * isString("moe"); // true - */ -function isString(value) { - return typeof value === "string"; -} -exports.isString = isString; - -/** - * Returns `true` if `value` is a number. - * @examples - * isNumber(8.4 * 5); // true - */ -function isNumber(value) { - return typeof value === "number"; -} -exports.isNumber = isNumber; - -/** - * Returns `true` if `value` is a `RegExp`. - * @examples - * isRegExp(/moe/); // true - */ -function isRegExp(value) { - return isObject(value) && instanceOf(value, RegExp); -} -exports.isRegExp = isRegExp; - -/** - * Returns true if `value` is a `Date`. - * @examples - * isDate(new Date()); // true - */ -function isDate(value) { - return isObject(value) && instanceOf(value, Date); -} -exports.isDate = isDate; - -/** - * Returns true if object is a Function. - * @examples - * isFunction(function foo(){}) // true - */ -function isFunction(value) { - return typeof value === "function"; -} -exports.isFunction = isFunction; - -/** - * Returns `true` if `value` is an object (please note that `null` is considered - * to be an atom and not an object). - * @examples - * isObject({}) // true - * isObject(null) // false - */ -function isObject(value) { - return typeof value === "object" && value !== null; -} -exports.isObject = isObject; - -/** - * Detect whether a value is a generator. - * - * @param aValue - * The value to identify. - * @return A boolean indicating whether the value is a generator. - */ -function isGenerator(aValue) { - return !!(aValue && aValue.isGenerator && aValue.isGenerator()); -} -exports.isGenerator = isGenerator; - -/** - * Returns true if `value` is an Array. - * @examples - * isArray([1, 2, 3]) // true - * isArray({ 0: 'foo', length: 1 }) // false - */ -var isArray = Array.isArray; -exports.isArray = isArray; - -/** - * Returns `true` if `value` is an Arguments object. - * @examples - * (function(){ return isArguments(arguments); })(1, 2, 3); // true - * isArguments([1,2,3]); // false - */ -function isArguments(value) { - return Object.prototype.toString.call(value) === "[object Arguments]"; -} -exports.isArguments = isArguments; - -var isMap = value => Object.prototype.toString.call(value) === "[object Map]" -exports.isMap = isMap; - -var isSet = value => Object.prototype.toString.call(value) === "[object Set]" -exports.isSet = isSet; - -/** - * Returns true if it is a primitive `value`. (null, undefined, number, - * boolean, string) - * @examples - * isPrimitive(3) // true - * isPrimitive('foo') // true - * isPrimitive({ bar: 3 }) // false - */ -function isPrimitive(value) { - return !isFunction(value) && !isObject(value); -} -exports.isPrimitive = isPrimitive; - -/** - * Returns `true` if given `object` is flat (it is direct decedent of - * `Object.prototype` or `null`). - * @examples - * isFlat({}) // true - * isFlat(new Type()) // false - */ -function isFlat(object) { - return isObject(object) && (isNull(Object.getPrototypeOf(object)) || - isNull(Object.getPrototypeOf( - Object.getPrototypeOf(object)))); -} -exports.isFlat = isFlat; - -/** - * Returns `true` if object contains no values. - */ -function isEmpty(object) { - if (isObject(object)) { - for (var key in object) - return false; - return true; - } - return false; -} -exports.isEmpty = isEmpty; - -/** - * Returns `true` if `value` is an array / flat object containing only atomic - * values and other flat objects. - */ -function isJSON(value, visited) { - // Adding value to array of visited values. - (visited || (visited = [])).push(value); - // If `value` is an atom return `true` cause it's valid JSON. - return isPrimitive(value) || - // If `value` is an array of JSON values that has not been visited - // yet. - (isArray(value) && value.every(function(element) { - return isJSON(element, visited); - })) || - // If `value` is a plain object containing properties with a JSON - // values it's a valid JSON. - (isFlat(value) && Object.keys(value).every(function(key) { - var $ = Object.getOwnPropertyDescriptor(value, key); - // Check every proprety of a plain object to verify that - // it's neither getter nor setter, but a JSON value, that - // has not been visited yet. - return ((!isObject($.value) || !~visited.indexOf($.value)) && - !('get' in $) && !('set' in $) && - isJSON($.value, visited)); - })); -} -exports.isJSON = function (value) { - return isJSON(value); -}; - -/** - * Returns `true` if `value` is JSONable - */ -const isJSONable = (value) => { - try { - JSON.parse(JSON.stringify(value)); - } - catch (e) { - return false; - } - return true; -}; -exports.isJSONable = isJSONable; - -/** - * Returns if `value` is an instance of a given `Type`. This is exactly same as - * `value instanceof Type` with a difference that `Type` can be from a scope - * that has a different top level object. (Like in case where `Type` is a - * function from different iframe / jetpack module / sandbox). - */ -function instanceOf(value, Type) { - var isConstructorNameSame; - var isConstructorSourceSame; - - // If `instanceof` returned `true` we know result right away. - var isInstanceOf = value instanceof Type; - - // If `instanceof` returned `false` we do ducktype check since `Type` may be - // from a different sandbox. If a constructor of the `value` or a constructor - // of the value's prototype has same name and source we assume that it's an - // instance of the Type. - if (!isInstanceOf && value) { - isConstructorNameSame = value.constructor.name === Type.name; - isConstructorSourceSame = String(value.constructor) == String(Type); - isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) || - instanceOf(Object.getPrototypeOf(value), Type); - } - return isInstanceOf; -} -exports.instanceOf = instanceOf; - -/** - * Function returns textual representation of a value passed to it. Function - * takes additional `indent` argument that is used for indentation. Also - * optional `limit` argument may be passed to limit amount of detail returned. - * @param {Object} value - * @param {String} [indent=" "] - * @param {Number} [limit] - */ -function source(value, indent, limit, offset, visited) { - var result; - var names; - var nestingIndex; - var isCompact = !isUndefined(limit); - - indent = indent || " "; - offset = (offset || ""); - result = ""; - visited = visited || []; - - if (isUndefined(value)) { - result += "undefined"; - } - else if (isNull(value)) { - result += "null"; - } - else if (isString(value)) { - result += '"' + value + '"'; - } - else if (isFunction(value)) { - value = String(value).split("\n"); - if (isCompact && value.length > 2) { - value = value.splice(0, 2); - value.push("...}"); - } - result += value.join("\n" + offset); - } - else if (isArray(value)) { - if ((nestingIndex = (visited.indexOf(value) + 1))) { - result = "#" + nestingIndex + "#"; - } - else { - visited.push(value); - - if (isCompact) - value = value.slice(0, limit); - - result += "[\n"; - result += value.map(function(value) { - return offset + indent + source(value, indent, limit, offset + indent, - visited); - }).join(",\n"); - result += isCompact && value.length > limit ? - ",\n" + offset + "...]" : "\n" + offset + "]"; - } - } - else if (isObject(value)) { - if ((nestingIndex = (visited.indexOf(value) + 1))) { - result = "#" + nestingIndex + "#" - } - else { - visited.push(value) - - names = Object.keys(value); - - result += "{ // " + value + "\n"; - result += (isCompact ? names.slice(0, limit) : names).map(function(name) { - var _limit = isCompact ? limit - 1 : limit; - var descriptor = Object.getOwnPropertyDescriptor(value, name); - var result = offset + indent + "// "; - var accessor; - if (0 <= name.indexOf(" ")) - name = '"' + name + '"'; - - if (descriptor.writable) - result += "writable "; - if (descriptor.configurable) - result += "configurable "; - if (descriptor.enumerable) - result += "enumerable "; - - result += "\n"; - if ("value" in descriptor) { - result += offset + indent + name + ": "; - result += source(descriptor.value, indent, _limit, indent + offset, - visited); - } - else { - - if (descriptor.get) { - result += offset + indent + "get " + name + " "; - accessor = source(descriptor.get, indent, _limit, indent + offset, - visited); - result += accessor.substr(accessor.indexOf("{")); - } - - if (descriptor.set) { - result += offset + indent + "set " + name + " "; - accessor = source(descriptor.set, indent, _limit, indent + offset, - visited); - result += accessor.substr(accessor.indexOf("{")); - } - } - return result; - }).join(",\n"); - - if (isCompact) { - if (names.length > limit && limit > 0) { - result += ",\n" + offset + indent + "//..."; - } - } - else { - if (names.length) - result += ","; - - result += "\n" + offset + indent + '"__proto__": '; - result += source(Object.getPrototypeOf(value), indent, 0, - offset + indent); - } - - result += "\n" + offset + "}"; - } - } - else { - result += String(value); - } - return result; -} -exports.source = function (value, indentation, limit) { - return source(value, indentation, limit); -}; diff --git a/addon-sdk/source/lib/sdk/lang/weak-set.js b/addon-sdk/source/lib/sdk/lang/weak-set.js deleted file mode 100644 index 8972602a5..000000000 --- a/addon-sdk/source/lib/sdk/lang/weak-set.js +++ /dev/null @@ -1,75 +0,0 @@ -/* 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/. */ - -module.metadata = { - "stability": "experimental" -}; - -"use strict"; - -const { Cu } = require("chrome"); - -function makeGetterFor(Type) { - let cache = new WeakMap(); - - return { - getFor(target) { - if (!cache.has(target)) - cache.set(target, new Type()); - - return cache.get(target); - }, - clearFor(target) { - return cache.delete(target) - } - } -} - -var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap); -var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set); - -function add(target, value) { - if (has(target, value)) - return; - - getLookupFor(target).set(value, true); - getRefsFor(target).add(Cu.getWeakReference(value)); -} -exports.add = add; - -function remove(target, value) { - getLookupFor(target).delete(value); -} -exports.remove = remove; - -function has(target, value) { - return getLookupFor(target).has(value); -} -exports.has = has; - -function clear(target) { - clearLookupFor(target); - clearRefsFor(target); -} -exports.clear = clear; - -function iterator(target) { - let refs = getRefsFor(target); - - for (let ref of refs) { - let value = ref.get(); - - // If `value` is already gc'ed, it would be `null`. - // The `has` function is using a WeakMap as lookup table, so passing `null` - // would raise an exception because WeakMap accepts as value only non-null - // object. - // Plus, if `value` is already gc'ed, we do not have to take it in account - // during the iteration, and remove it from the references. - if (value !== null && has(target, value)) - yield value; - else - refs.delete(ref); - } -} -exports.iterator = iterator; diff --git a/addon-sdk/source/lib/sdk/loader/cuddlefish.js b/addon-sdk/source/lib/sdk/loader/cuddlefish.js deleted file mode 100644 index 6ba19157b..000000000 --- a/addon-sdk/source/lib/sdk/loader/cuddlefish.js +++ /dev/null @@ -1,102 +0,0 @@ -/* 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" -}; - -// This module is manually loaded by bootstrap.js in a sandbox and immediatly -// put in module cache so that it is never loaded in any other way. - -/* Workarounds to include dependencies in the manifest -require('chrome') // Otherwise CFX will complain about Components -require('toolkit/loader') // Otherwise CFX will stip out loader.js -require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.js -*/ - -const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components; - -// `loadSandbox` is exposed by bootstrap.js -const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js", - "toolkit/loader.js"); -const xulappURI = module.uri.replace("loader/cuddlefish.js", - "system/xul-app.jsm"); -// We need to keep a reference to the sandbox in order to unload it in -// bootstrap.js - -var loaderSandbox = loadSandbox(loaderURI); -const loaderModule = loaderSandbox.exports; - -const { incompatibility } = Cu.import(xulappURI, {}).XulApp; - -const { override, load } = loaderModule; - -function CuddlefishLoader(options) { - let { manifest } = options; - - options = override(options, { - // Put `api-utils/loader` and `api-utils/cuddlefish` loaded as JSM to module - // cache to avoid subsequent loads via `require`. - modules: override({ - 'toolkit/loader': loaderModule, - 'sdk/loader/cuddlefish': exports - }, options.modules), - resolve: function resolve(id, requirer) { - let entry = requirer && requirer in manifest && manifest[requirer]; - let uri = null; - - // If manifest entry for this requirement is present we follow manifest. - // Note: Standard library modules like 'panel' will be present in - // manifest unless they were moved to platform. - if (entry) { - let requirement = entry.requirements[id]; - // If requirer entry is in manifest and it's requirement is not, than - // it has no authority to load since linker was not able to find it. - if (!requirement) - throw Error('Module: ' + requirer + ' has no authority to load: ' - + id, requirer); - - uri = requirement; - } else { - // If requirer is off manifest than it's a system module and we allow it - // to go off manifest by resolving a relative path. - uri = loaderModule.resolve(id, requirer); - } - return uri; - }, - load: function(loader, module) { - let result; - let error; - - // In order to get the module's metadata, we need to load the module. - // if an exception is raised here, it could be that is due to application - // incompatibility. Therefore the exception is stored, and thrown again - // only if the module seems be compatible with the application currently - // running. Otherwise the incompatibility message takes the precedence. - try { - result = load(loader, module); - } - catch (e) { - error = e; - } - - error = incompatibility(module) || error; - - if (error) - throw error; - - return result; - } - }); - - let loader = loaderModule.Loader(options); - // Hack to allow loading from `toolkit/loader`. - loader.modules[loaderURI] = loaderSandbox; - return loader; -} - -exports = override(loaderModule, { - Loader: CuddlefishLoader -}); diff --git a/addon-sdk/source/lib/sdk/loader/sandbox.js b/addon-sdk/source/lib/sdk/loader/sandbox.js deleted file mode 100644 index 791dbc086..000000000 --- a/addon-sdk/source/lib/sdk/loader/sandbox.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, CC, Cu } = require('chrome'); -const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); -const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. - getService(Ci.mozIJSSubScriptLoader); -const self = require('sdk/self'); -const { getTabId } = require('../tabs/utils'); -const { getInnerId } = require('../window/utils'); - -const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {}); -const { require: devtoolsRequire } = devtools; -const { addContentGlobal, removeContentGlobal } = devtoolsRequire("devtools/server/content-globals"); - -/** - * Make a new sandbox that inherits given `source`'s principals. Source can be - * URI string, DOMWindow or `null` for system principals. - */ -function sandbox(target, options) { - options = options || {}; - options.metadata = options.metadata ? options.metadata : {}; - options.metadata.addonID = options.metadata.addonID ? - options.metadata.addonID : self.id; - - let sandbox = Cu.Sandbox(target || systemPrincipal, options); - Cu.setSandboxMetadata(sandbox, options.metadata); - let innerWindowID = options.metadata['inner-window-id'] - if (innerWindowID) { - addContentGlobal({ - global: sandbox, - 'inner-window-id': innerWindowID - }); - } - return sandbox; -} -exports.sandbox = sandbox; - -/** - * Evaluates given `source` in a given `sandbox` and returns result. - */ -function evaluate(sandbox, code, uri, line, version) { - return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1); -} -exports.evaluate = evaluate; - -/** - * Evaluates code under the given `uri` in the given `sandbox`. - * - * @param {String} uri - * The URL pointing to the script to load. - * It must be a local chrome:, resource:, file: or data: URL. - */ -function load(sandbox, uri) { - if (uri.indexOf('data:') === 0) { - let source = uri.substr(uri.indexOf(',') + 1); - - return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0); - } else { - return scriptLoader.loadSubScript(uri, sandbox, 'UTF-8'); - } -} -exports.load = load; - -/** - * Forces the given `sandbox` to be freed immediately. - */ -exports.nuke = Cu.nukeSandbox diff --git a/addon-sdk/source/lib/sdk/messaging.js b/addon-sdk/source/lib/sdk/messaging.js deleted file mode 100644 index 07580eb33..000000000 --- a/addon-sdk/source/lib/sdk/messaging.js +++ /dev/null @@ -1,12 +0,0 @@ -/* 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 { window } = require("sdk/addon/window"); -exports.MessageChannel = window.MessageChannel; -exports.MessagePort = window.MessagePort; diff --git a/addon-sdk/source/lib/sdk/model/core.js b/addon-sdk/source/lib/sdk/model/core.js deleted file mode 100644 index 315f8b1cd..000000000 --- a/addon-sdk/source/lib/sdk/model/core.js +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 { dispatcher } = require("../util/dispatcher"); - - -// Define `modelFor` accessor function that can be implemented -// for different types of views. Since view's we'll be dealing -// with types that don't really play well with `instanceof` -// operator we're gonig to use `dispatcher` that is slight -// extension over polymorphic dispatch provided by method. -// This allows models to extend implementations of this by -// providing predicates: -// -// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id)) -const modelFor = dispatcher("modelFor"); -exports.modelFor = modelFor; diff --git a/addon-sdk/source/lib/sdk/net/url.js b/addon-sdk/source/lib/sdk/net/url.js deleted file mode 100644 index 5502171ee..000000000 --- a/addon-sdk/source/lib/sdk/net/url.js +++ /dev/null @@ -1,94 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci, Cu, components } = require("chrome"); - -const { defer } = require("../core/promise"); -const { merge } = require("../util/object"); - -const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); -const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -/** - * Reads a URI and returns a promise. - * - * @param uri {string} The URI to read - * @param [options] {object} This parameter can have any or all of the following - * fields: `charset`. By default the `charset` is set to 'UTF-8'. - * - * @returns {promise} The promise that will be resolved with the content of the - * URL given. - * - * @example - * let promise = readURI('resource://gre/modules/NetUtil.jsm', { - * charset: 'US-ASCII' - * }); - */ -function readURI(uri, options) { - options = options || {}; - let charset = options.charset || 'UTF-8'; - - let channel = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, charset), - loadUsingSystemPrincipal: true}); - - let { promise, resolve, reject } = defer(); - - try { - NetUtil.asyncFetch(channel, function (stream, result) { - if (components.isSuccessCode(result)) { - let count = stream.available(); - let data = NetUtil.readInputStreamToString(stream, count, { charset : charset }); - - resolve(data); - } else { - reject("Failed to read: '" + uri + "' (Error Code: " + result + ")"); - } - }); - } - catch (e) { - reject("Failed to read: '" + uri + "' (Error: " + e.message + ")"); - } - - return promise; -} - -exports.readURI = readURI; - -/** - * Reads a URI synchronously. - * This function is intentionally undocumented to favorites the `readURI` usage. - * - * @param uri {string} The URI to read - * @param [charset] {string} The character set to use when read the content of - * the `uri` given. By default is set to 'UTF-8'. - * - * @returns {string} The content of the URI given. - * - * @example - * let data = readURISync('resource://gre/modules/NetUtil.jsm'); - */ -function readURISync(uri, charset) { - charset = typeof charset === "string" ? charset : "UTF-8"; - - let channel = NetUtil.newChannel({ - uri: NetUtil.newURI(uri, charset), - loadUsingSystemPrincipal: true}); - let stream = channel.open2(); - - let count = stream.available(); - let data = NetUtil.readInputStreamToString(stream, count, { charset : charset }); - - stream.close(); - - return data; -} - -exports.readURISync = readURISync; diff --git a/addon-sdk/source/lib/sdk/net/xhr.js b/addon-sdk/source/lib/sdk/net/xhr.js deleted file mode 100644 index 415b9cbf4..000000000 --- a/addon-sdk/source/lib/sdk/net/xhr.js +++ /dev/null @@ -1,36 +0,0 @@ -/* 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": "stable" -}; - -const { deprecateFunction } = require("../util/deprecate"); -const { Cc, Ci } = require("chrome"); -const XMLHttpRequest = require("../addon/window").window.XMLHttpRequest; - -Object.defineProperties(XMLHttpRequest.prototype, { - mozBackgroundRequest: { - value: true, - }, - forceAllowThirdPartyCookie: { - configurable: true, - value: deprecateFunction(function() { - forceAllowThirdPartyCookie(this); - - }, "`xhr.forceAllowThirdPartyCookie()` is deprecated, please use" + - "`require('sdk/net/xhr').forceAllowThirdPartyCookie(request)` instead") - } -}); -exports.XMLHttpRequest = XMLHttpRequest; - -function forceAllowThirdPartyCookie(xhr) { - if (xhr.channel instanceof Ci.nsIHttpChannelInternal) - xhr.channel.forceAllowThirdPartyCookie = true; -} -exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie; - -// No need to handle add-on unloads as addon/window is closed at unload -// and it will take down all the associated requests. diff --git a/addon-sdk/source/lib/sdk/notifications.js b/addon-sdk/source/lib/sdk/notifications.js deleted file mode 100644 index 752e08fb1..000000000 --- a/addon-sdk/source/lib/sdk/notifications.js +++ /dev/null @@ -1,112 +0,0 @@ -/* 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": "stable" -}; - -const { Cc, Ci, Cr } = require("chrome"); -const apiUtils = require("./deprecated/api-utils"); -const { isString, isUndefined, instanceOf } = require('./lang/type'); -const { URL, isLocalURL } = require('./url'); -const { data } = require('./self'); - -const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"]; - -try { - let alertServ = Cc["@mozilla.org/alerts-service;1"]. - getService(Ci.nsIAlertsService); - - // The unit test sets this to a mock notification function. - var notify = alertServ.showAlertNotification.bind(alertServ); -} -catch (err) { - // An exception will be thrown if the platform doesn't provide an alert - // service, e.g., if Growl is not installed on OS X. In that case, use a - // mock notification function that just logs to the console. - notify = notifyUsingConsole; -} - -exports.notify = function notifications_notify(options) { - let valOpts = validateOptions(options); - let clickObserver = !valOpts.onClick ? null : { - observe: (subject, topic, data) => { - if (topic === "alertclickcallback") { - try { - valOpts.onClick.call(exports, valOpts.data); - } - catch(e) { - console.exception(e); - } - } - } - }; - function notifyWithOpts(notifyFn) { - let { iconURL } = valOpts; - iconURL = iconURL && isLocalURL(iconURL) ? data.url(iconURL) : iconURL; - - notifyFn(iconURL, valOpts.title, valOpts.text, !!clickObserver, - valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang); - } - try { - notifyWithOpts(notify); - } - catch (err) { - if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) { - console.warn("The notification icon named by " + iconURL + - " does not exist. A default icon will be used instead."); - delete valOpts.iconURL; - notifyWithOpts(notify); - } - else { - notifyWithOpts(notifyUsingConsole); - } - } -}; - -function notifyUsingConsole(iconURL, title, text) { - title = title ? "[" + title + "]" : ""; - text = text || ""; - let str = [title, text].filter(s => s).join(" "); - console.log(str); -} - -function validateOptions(options) { - return apiUtils.validateOptions(options, { - data: { - is: ["string", "undefined"] - }, - iconURL: { - is: ["string", "undefined", "object"], - ok: function(value) { - return isUndefined(value) || isString(value) || (value instanceof URL); - }, - msg: "`iconURL` must be a string or an URL instance." - }, - onClick: { - is: ["function", "undefined"] - }, - text: { - is: ["string", "undefined", "number"] - }, - title: { - is: ["string", "undefined", "number"] - }, - tag: { - is: ["string", "undefined", "number"] - }, - dir: { - is: ["string", "undefined"], - ok: function(value) { - return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value); - }, - msg: '`dir` option must be one of: "auto", "ltr" or "rtl".' - }, - lang: { - is: ["string", "undefined"] - } - }); -} diff --git a/addon-sdk/source/lib/sdk/output/system.js b/addon-sdk/source/lib/sdk/output/system.js deleted file mode 100644 index 4fb16dcd5..000000000 --- a/addon-sdk/source/lib/sdk/output/system.js +++ /dev/null @@ -1,71 +0,0 @@ -/* 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, Cr } = require("chrome"); -const { Input, start, stop, receive, outputs } = require("../event/utils"); -const { id: addonID } = require("../self"); -const { setImmediate } = require("../timers"); -const { notifyObservers } = Cc['@mozilla.org/observer-service;1']. - getService(Ci.nsIObserverService); - -const NOT_AN_INPUT = "OutputPort can be used only for sending messages"; - -// `OutputPort` creates a port to which messages can be send. Those -// messages are actually disptached as `subject`'s of the observer -// notifications. This is handy for communicating between different -// components of the SDK. By default messages are dispatched -// asynchronously, although `options.sync` can be used to make them -// synchronous. If `options.id` is given `topic` for observer -// notifications is generated by namespacing it, to avoid spamming -// other SDK add-ons. It's also possible to provide `options.topic` -// to use excat `topic` without namespacing it. -// -// Note: Symmetric `new InputPort({ id: "x" })` instances can be used to -// receive messages send to the instances of `new OutputPort({ id: "x" })`. -const OutputPort = function({id, topic, sync}) { - this.id = id || topic; - this.sync = !!sync; - this.topic = topic || "sdk:" + addonID + ":" + id; -}; -// OutputPort extends base signal type to implement same message -// receiving interface. -OutputPort.prototype = new Input(); -OutputPort.constructor = OutputPort; - -// OutputPort can not be consumed there for starting or stopping it -// is not supported. -OutputPort.prototype[start] = _ => { throw TypeError(NOT_AN_INPUT); }; -OutputPort.prototype[stop] = _ => { throw TypeError(NOT_AN_INPUT); }; - -// Port reecives message send to it, which will be dispatched via -// observer notification service. -OutputPort.receive = ({topic, sync}, message) => { - const type = typeof(message); - const supported = message === null || - type === "object" || - type === "function"; - - // There is no sensible way to wrap JS primitives that would make sense - // for general observer notification users. It's also probably not very - // useful to dispatch JS primitives as subject of observer service, there - // for we do not support those use cases. - if (!supported) - throw new TypeError("Unsupproted message type: `" + type + "`"); - - // Normalize `message` to create a valid observer notification `subject`. - // If `message` is `null`, implements `nsISupports` interface or already - // represents wrapped JS object use it as is. Otherwise create a wrapped - // object so that observers could receive it. - const subject = message === null ? null : - message instanceof Ci.nsISupports ? message : - message.wrappedJSObject ? message : - {wrappedJSObject: message}; - if (sync) - notifyObservers(subject, topic, null); - else - setImmediate(notifyObservers, subject, topic, null); -}; -OutputPort.prototype[receive] = OutputPort.receive; -exports.OutputPort = OutputPort; diff --git a/addon-sdk/source/lib/sdk/page-mod.js b/addon-sdk/source/lib/sdk/page-mod.js deleted file mode 100644 index 538be2732..000000000 --- a/addon-sdk/source/lib/sdk/page-mod.js +++ /dev/null @@ -1,190 +0,0 @@ -/* 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": "stable" -}; - -const { contract: loaderContract } = require('./content/loader'); -const { contract } = require('./util/contract'); -const { WorkerHost, connect } = require('./content/utils'); -const { Class } = require('./core/heritage'); -const { Disposable } = require('./core/disposable'); -const { Worker } = require('./content/worker'); -const { EventTarget } = require('./event/target'); -const { on, emit, once, setListeners } = require('./event/core'); -const { isRegExp, isUndefined } = require('./lang/type'); -const { merge, omit } = require('./util/object'); -const { remove, has, hasAny } = require("./util/array"); -const { Rules } = require("./util/rules"); -const { processes, frames, remoteRequire } = require('./remote/parent'); -remoteRequire('sdk/content/page-mod'); - -const pagemods = new Map(); -const workers = new Map(); -const models = new WeakMap(); -var modelFor = (mod) => models.get(mod); -var workerFor = (mod) => workers.get(mod)[0]; - -// Helper functions -var isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string'; - -var PAGEMOD_ID = 0; - -// Validation Contracts -const modOptions = { - // contentStyle* / contentScript* are sharing the same validation constraints, - // so they can be mostly reused, except for the messages. - contentStyle: merge(Object.create(loaderContract.rules.contentScript), { - msg: 'The `contentStyle` option must be a string or an array of strings.' - }), - contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), { - msg: 'The `contentStyleFile` option must be a local URL or an array of URLs' - }), - include: { - is: ['string', 'array', 'regexp'], - ok: (rule) => { - if (isRegExpOrString(rule)) - return true; - if (Array.isArray(rule) && rule.length > 0) - return rule.every(isRegExpOrString); - return false; - }, - msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.' - }, - exclude: { - is: ['string', 'array', 'regexp', 'undefined'], - ok: (rule) => { - if (isRegExpOrString(rule) || isUndefined(rule)) - return true; - if (Array.isArray(rule) && rule.length > 0) - return rule.every(isRegExpOrString); - return false; - }, - msg: 'If set, the `exclude` option must always contain at least one ' + - 'rule as a string, regular expression, or an array of strings and ' + - 'regular expressions.' - }, - attachTo: { - is: ['string', 'array', 'undefined'], - map: function (attachTo) { - if (!attachTo) return ['top', 'frame']; - if (typeof attachTo === 'string') return [attachTo]; - return attachTo; - }, - ok: function (attachTo) { - return hasAny(attachTo, ['top', 'frame']) && - attachTo.every(has.bind(null, ['top', 'frame', 'existing'])); - }, - msg: 'The `attachTo` option must be a string or an array of strings. ' + - 'The only valid options are "existing", "top" and "frame", and must ' + - 'contain at least "top" or "frame" values.' - }, -}; - -const modContract = contract(merge({}, loaderContract.rules, modOptions)); - -/** - * PageMod constructor (exported below). - * @constructor - */ -const PageMod = Class({ - implements: [ - modContract.properties(modelFor), - EventTarget, - Disposable, - ], - extends: WorkerHost(workerFor), - setup: function PageMod(options) { - let mod = this; - let model = modContract(options); - models.set(this, model); - model.id = PAGEMOD_ID++; - - let include = model.include; - model.include = Rules(); - model.include.add.apply(model.include, [].concat(include)); - - let exclude = isUndefined(model.exclude) ? [] : model.exclude; - model.exclude = Rules(); - model.exclude.add.apply(model.exclude, [].concat(exclude)); - - // Set listeners on {PageMod} itself, not the underlying worker, - // like `onMessage`, as it'll get piped. - setListeners(this, options); - - pagemods.set(model.id, this); - workers.set(this, []); - - function serializeRules(rules) { - for (let rule of rules) { - yield isRegExp(rule) ? { type: "regexp", pattern: rule.source, flags: rule.flags } - : { type: "string", value: rule }; - } - } - - model.childOptions = omit(model, ["include", "exclude", "contentScriptOptions"]); - model.childOptions.include = [...serializeRules(model.include)]; - model.childOptions.exclude = [...serializeRules(model.exclude)]; - model.childOptions.contentScriptOptions = model.contentScriptOptions ? - JSON.stringify(model.contentScriptOptions) : - null; - - processes.port.emit('sdk/page-mod/create', model.childOptions); - }, - - dispose: function(reason) { - processes.port.emit('sdk/page-mod/destroy', modelFor(this).id); - pagemods.delete(modelFor(this).id); - workers.delete(this); - }, - - destroy: function(reason) { - // Explicit destroy call, i.e. not via unload so destroy the workers - let list = workers.get(this); - if (!list) - return; - - // Triggers dispose which will cause the child page-mod to be destroyed - Disposable.prototype.destroy.call(this, reason); - - // Destroy any active workers - for (let worker of list) - worker.destroy(reason); - } -}); -exports.PageMod = PageMod; - -// Whenever a new process starts send over the list of page-mods -processes.forEvery(process => { - for (let mod of pagemods.values()) - process.port.emit('sdk/page-mod/create', modelFor(mod).childOptions); -}); - -frames.port.on('sdk/page-mod/worker-create', (frame, modId, workerOptions) => { - let mod = pagemods.get(modId); - if (!mod) - return; - - // Attach the parent side of the worker to the child - let worker = Worker(); - - workers.get(mod).unshift(worker); - worker.on('*', (event, ...args) => { - // page-mod's "attach" event needs to be passed a worker - if (event === 'attach') - emit(mod, event, worker) - else - emit(mod, event, ...args); - }); - - worker.on('detach', () => { - let array = workers.get(mod); - if (array) - remove(array, worker); - }); - - connect(worker, frame, workerOptions); -}); diff --git a/addon-sdk/source/lib/sdk/page-mod/match-pattern.js b/addon-sdk/source/lib/sdk/page-mod/match-pattern.js deleted file mode 100644 index afbbd401e..000000000 --- a/addon-sdk/source/lib/sdk/page-mod/match-pattern.js +++ /dev/null @@ -1,10 +0,0 @@ -/* 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 { deprecateUsage } = require("../util/deprecate"); - -deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead"); - -module.exports = require("../util/match-pattern"); diff --git a/addon-sdk/source/lib/sdk/page-worker.js b/addon-sdk/source/lib/sdk/page-worker.js deleted file mode 100644 index 837cf774b..000000000 --- a/addon-sdk/source/lib/sdk/page-worker.js +++ /dev/null @@ -1,194 +0,0 @@ -/* 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": "stable" -}; - -const { Class } = require('./core/heritage'); -const { ns } = require('./core/namespace'); -const { pipe, stripListeners } = require('./event/utils'); -const { connect, destroy, WorkerHost } = require('./content/utils'); -const { Worker } = require('./content/worker'); -const { Disposable } = require('./core/disposable'); -const { EventTarget } = require('./event/target'); -const { setListeners } = require('./event/core'); -const { window } = require('./addon/window'); -const { create: makeFrame, getDocShell } = require('./frame/utils'); -const { contract } = require('./util/contract'); -const { contract: loaderContract } = require('./content/loader'); -const { Rules } = require('./util/rules'); -const { merge } = require('./util/object'); -const { uuid } = require('./util/uuid'); -const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent"); -remoteRequire("sdk/content/page-worker"); - -const workers = new WeakMap(); -const pages = new Map(); - -const internal = ns(); - -let workerFor = (page) => workers.get(page); -let isDisposed = (page) => !pages.has(internal(page).id); - -// The frame is used to ensure we have a remote process to load workers in -let remoteFrame = null; -let framePromise = null; -function getFrame() { - if (framePromise) - return framePromise; - - framePromise = new Promise(resolve => { - let view = makeFrame(window.document, { - namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", - nodeName: "iframe", - type: "content", - remote: useRemoteProcesses, - uri: "about:blank" - }); - - // Wait for the remote side to connect - let listener = (frame) => { - if (frame.frameElement != view) - return; - frames.off("attach", listener); - remoteFrame = frame; - resolve(frame); - } - frames.on("attach", listener); - }); - return framePromise; -} - -var pageContract = contract(merge({ - allow: { - is: ['object', 'undefined', 'null'], - map: function (allow) { return { script: !allow || allow.script !== false }} - }, - onMessage: { - is: ['function', 'undefined'] - }, - include: { - is: ['string', 'array', 'regexp', 'undefined'] - }, - contentScriptWhen: { - is: ['string', 'undefined'], - map: (when) => when || "end" - } -}, loaderContract.rules)); - -function enableScript (page) { - getDocShell(viewFor(page)).allowJavascript = true; -} - -function disableScript (page) { - getDocShell(viewFor(page)).allowJavascript = false; -} - -function Allow (page) { - return { - get script() { - return internal(page).options.allow.script; - }, - set script(value) { - internal(page).options.allow.script = value; - - if (isDisposed(page)) - return; - - remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value }); - } - }; -} - -function isValidURL(page, url) { - return !page.rules || page.rules.matchesAny(url); -} - -const Page = Class({ - implements: [ - EventTarget, - Disposable - ], - extends: WorkerHost(workerFor), - setup: function Page(options) { - options = pageContract(options); - // Sanitize the options - if ("contentScriptOptions" in options) - options.contentScriptOptions = JSON.stringify(options.contentScriptOptions); - - internal(this).id = uuid().toString(); - internal(this).options = options; - - for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) { - this[prop] = options[prop]; - } - - pages.set(internal(this).id, this); - - // Set listeners on the {Page} object itself, not the underlying worker, - // like `onMessage`, as it gets piped - setListeners(this, options); - let worker = new Worker(stripListeners(options)); - workers.set(this, worker); - pipe(worker, this); - - if (options.include) { - this.rules = Rules(); - this.rules.add.apply(this.rules, [].concat(options.include)); - } - - getFrame().then(frame => { - if (isDisposed(this)) - return; - - frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options)); - }); - }, - get allow() { return Allow(this); }, - set allow(value) { - if (isDisposed(this)) - return; - this.allow.script = pageContract({ allow: value }).allow.script; - }, - get contentURL() { - return internal(this).options.contentURL; - }, - set contentURL(value) { - if (!isValidURL(this, value)) - return; - internal(this).options.contentURL = value; - if (isDisposed(this)) - return; - - remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value }); - }, - dispose: function () { - if (isDisposed(this)) - return; - pages.delete(internal(this).id); - let worker = workerFor(this); - if (worker) - destroy(worker); - remoteFrame.port.emit("sdk/frame/destroy", internal(this).id); - - // Destroy the remote frame if all the pages have been destroyed - if (pages.size == 0) { - framePromise = null; - remoteFrame.frameElement.remove(); - remoteFrame = null; - } - }, - toString: function () { return '[object Page]' } -}); - -exports.Page = Page; - -frames.port.on("sdk/frame/connect", (frame, id, params) => { - let page = pages.get(id); - if (!page) - return; - connect(workerFor(page), frame, params); -}); diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js deleted file mode 100644 index 4b625799d..000000000 --- a/addon-sdk/source/lib/sdk/panel.js +++ /dev/null @@ -1,427 +0,0 @@ -/* 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"; - -// The panel module currently supports only Firefox and SeaMonkey. -// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps -module.metadata = { - "stability": "stable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cu, Ci } = require("chrome"); -const { setTimeout } = require('./timers'); -const { Class } = require("./core/heritage"); -const { merge } = require("./util/object"); -const { WorkerHost } = require("./content/utils"); -const { Worker } = require("./deprecated/sync-worker"); -const { Disposable } = require("./core/disposable"); -const { WeakReference } = require('./core/reference'); -const { contract: loaderContract } = require("./content/loader"); -const { contract } = require("./util/contract"); -const { on, off, emit, setListeners } = require("./event/core"); -const { EventTarget } = require("./event/target"); -const domPanel = require("./panel/utils"); -const { getDocShell } = require('./frame/utils'); -const { events } = require("./panel/events"); -const systemEvents = require("./system/events"); -const { filter, pipe, stripListeners } = require("./event/utils"); -const { getNodeView, getActiveView } = require("./view/core"); -const { isNil, isObject, isNumber } = require("./lang/type"); -const { getAttachEventType } = require("./content/utils"); -const { number, boolean, object } = require('./deprecated/api-utils'); -const { Style } = require("./stylesheet/style"); -const { attach, detach } = require("./content/mod"); - -var isRect = ({top, right, bottom, left}) => [top, right, bottom, left]. - some(value => isNumber(value) && !isNaN(value)); - -var isSDKObj = obj => obj instanceof Class; - -var rectContract = contract({ - top: number, - right: number, - bottom: number, - left: number -}); - -var position = { - is: object, - map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v), - ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)), - msg: 'The option "position" must be a SDK object registered as anchor; ' + - 'or an object with one or more of the following keys set to numeric ' + - 'values: top, right, bottom, left.' -} - -var displayContract = contract({ - width: number, - height: number, - focus: boolean, - position: position -}); - -var panelContract = contract(merge({ - // contentStyle* / contentScript* are sharing the same validation constraints, - // so they can be mostly reused, except for the messages. - contentStyle: merge(Object.create(loaderContract.rules.contentScript), { - msg: 'The `contentStyle` option must be a string or an array of strings.' - }), - contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), { - msg: 'The `contentStyleFile` option must be a local URL or an array of URLs' - }), - contextMenu: boolean, - allow: { - is: ['object', 'undefined', 'null'], - map: function (allow) { return { script: !allow || allow.script !== false }} - }, -}, displayContract.rules, loaderContract.rules)); - -function Allow(panel) { - return { - get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; }, - set script(value) { return setScriptState(panel, value); }, - }; -} - -function setScriptState(panel, value) { - let view = viewFor(panel); - getDocShell(view.backgroundFrame).allowJavascript = value; - getDocShell(view.viewFrame).allowJavascript = value; - view.setAttribute("sdkscriptenabled", "" + value); -} - -function isDisposed(panel) { - return !views.has(panel); -} - -var panels = new WeakMap(); -var models = new WeakMap(); -var views = new WeakMap(); -var workers = new WeakMap(); -var styles = new WeakMap(); - -const viewFor = (panel) => views.get(panel); -const modelFor = (panel) => models.get(panel); -const panelFor = (view) => panels.get(view); -const workerFor = (panel) => workers.get(panel); -const styleFor = (panel) => styles.get(panel); - -function getPanelFromWeakRef(weakRef) { - if (!weakRef) { - return null; - } - let panel = weakRef.get(); - if (!panel) { - return null; - } - if (isDisposed(panel)) { - return null; - } - return panel; -} - -var SinglePanelManager = { - visiblePanel: null, - enqueuedPanel: null, - enqueuedPanelCallback: null, - // Calls |callback| with no arguments when the panel may be shown. - requestOpen: function(panelToOpen, callback) { - let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel); - if (currentPanel || SinglePanelManager.enqueuedPanel) { - SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen); - SinglePanelManager.enqueuedPanelCallback = callback; - if (currentPanel && currentPanel.isShowing) { - currentPanel.hide(); - } - } else { - SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback); - } - }, - notifyPanelCanOpen: function(panel, callback) { - let view = viewFor(panel); - // Can't pass an arrow function as the event handler because we need to be - // able to call |removeEventListener| later. - view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true); - view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false); - SinglePanelManager.enqueuedPanel = null; - SinglePanelManager.enqueuedPanelCallback = null; - SinglePanelManager.visiblePanel = Cu.getWeakReference(panel); - callback(); - }, - onVisiblePanelShown: function(event) { - let panel = panelFor(event.target); - if (SinglePanelManager.enqueuedPanel) { - // Another panel started waiting for |panel| to close before |panel| was - // even done opening. - panel.hide(); - } - }, - onVisiblePanelHidden: function(event) { - let view = event.target; - let panel = panelFor(view); - let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel); - if (currentPanel && currentPanel != panel) { - return; - } - SinglePanelManager.visiblePanel = null; - view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true); - view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false); - let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel); - let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback; - if (nextPanel) { - SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback); - } - } -}; - -const Panel = Class({ - implements: [ - // Generate accessors for the validated properties that update model on - // set and return values from model on get. - panelContract.properties(modelFor), - EventTarget, - Disposable, - WeakReference - ], - extends: WorkerHost(workerFor), - setup: function setup(options) { - let model = merge({ - defaultWidth: 320, - defaultHeight: 240, - focus: true, - position: Object.freeze({}), - contextMenu: false - }, panelContract(options)); - model.ready = false; - models.set(this, model); - - if (model.contentStyle || model.contentStyleFile) { - styles.set(this, Style({ - uri: model.contentStyleFile, - source: model.contentStyle - })); - } - - // Setup view - let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)}; - let view = domPanel.make(null, viewOptions); - panels.set(view, this); - views.set(this, view); - - // Load panel content. - domPanel.setURL(view, model.contentURL); - - // Allow context menu - domPanel.allowContextMenu(view, model.contextMenu); - - // Setup listeners. - setListeners(this, options); - let worker = new Worker(stripListeners(options)); - workers.set(this, worker); - - // pipe events from worker to a panel. - pipe(worker, this); - }, - dispose: function dispose() { - this.hide(); - off(this); - - workerFor(this).destroy(); - detach(styleFor(this)); - - domPanel.dispose(viewFor(this)); - - // Release circular reference between view and panel instance. This - // way view will be GC-ed. And panel as well once all the other refs - // will be removed from it. - views.delete(this); - }, - /* Public API: Panel.width */ - get width() { - return modelFor(this).width; - }, - set width(value) { - this.resize(value, this.height); - }, - /* Public API: Panel.height */ - get height() { - return modelFor(this).height; - }, - set height(value) { - this.resize(this.width, value); - }, - - /* Public API: Panel.focus */ - get focus() { - return modelFor(this).focus; - }, - - /* Public API: Panel.position */ - get position() { - return modelFor(this).position; - }, - - /* Public API: Panel.contextMenu */ - get contextMenu() { - return modelFor(this).contextMenu; - }, - set contextMenu(allow) { - let model = modelFor(this); - model.contextMenu = panelContract({ contextMenu: allow }).contextMenu; - domPanel.allowContextMenu(viewFor(this), model.contextMenu); - }, - - get contentURL() { - return modelFor(this).contentURL; - }, - set contentURL(value) { - let model = modelFor(this); - model.contentURL = panelContract({ contentURL: value }).contentURL; - domPanel.setURL(viewFor(this), model.contentURL); - // Detach worker so that messages send will be queued until it's - // reatached once panel content is ready. - workerFor(this).detach(); - }, - - get allow() { return Allow(this); }, - set allow(value) { - let allowJavascript = panelContract({ allow: value }).allow.script; - return setScriptState(this, value); - }, - - /* Public API: Panel.isShowing */ - get isShowing() { - return !isDisposed(this) && domPanel.isOpen(viewFor(this)); - }, - - /* Public API: Panel.show */ - show: function show(options={}, anchor) { - SinglePanelManager.requestOpen(this, () => { - if (options instanceof Ci.nsIDOMElement) { - [anchor, options] = [options, null]; - } - - if (anchor instanceof Ci.nsIDOMElement) { - console.warn( - "Passing a DOM node to Panel.show() method is an unsupported " + - "feature that will be soon replaced. " + - "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877" - ); - } - - let model = modelFor(this); - let view = viewFor(this); - let anchorView = getNodeView(anchor || options.position || model.position); - - options = merge({ - position: model.position, - width: model.width, - height: model.height, - defaultWidth: model.defaultWidth, - defaultHeight: model.defaultHeight, - focus: model.focus, - contextMenu: model.contextMenu - }, displayContract(options)); - - if (!isDisposed(this)) { - domPanel.show(view, options, anchorView); - } - }); - return this; - }, - - /* Public API: Panel.hide */ - hide: function hide() { - // Quit immediately if panel is disposed or there is no state change. - domPanel.close(viewFor(this)); - - return this; - }, - - /* Public API: Panel.resize */ - resize: function resize(width, height) { - let model = modelFor(this); - let view = viewFor(this); - let change = panelContract({ - width: width || model.width || model.defaultWidth, - height: height || model.height || model.defaultHeight - }); - - model.width = change.width - model.height = change.height - - domPanel.resize(view, model.width, model.height); - - return this; - } -}); -exports.Panel = Panel; - -// Note must be defined only after value to `Panel` is assigned. -getActiveView.define(Panel, viewFor); - -// Filter panel events to only panels that are create by this module. -var panelEvents = filter(events, ({target}) => panelFor(target)); - -// Panel events emitted after panel has being shown. -var shows = filter(panelEvents, ({type}) => type === "popupshown"); - -// Panel events emitted after panel became hidden. -var hides = filter(panelEvents, ({type}) => type === "popuphidden"); - -// Panel events emitted after content inside panel is ready. For different -// panels ready may mean different state based on `contentScriptWhen` attribute. -// Weather given event represents readyness is detected by `getAttachEventType` -// helper function. -var ready = filter(panelEvents, ({type, target}) => - getAttachEventType(modelFor(panelFor(target))) === type); - -// Panel event emitted when the contents of the panel has been loaded. -var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded"); - -// Styles should be always added as soon as possible, and doesn't makes them -// depends on `contentScriptWhen` -var start = filter(panelEvents, ({type}) => type === "document-element-inserted"); - -// Forward panel show / hide events to panel's own event listeners. -on(shows, "data", ({target}) => { - let panel = panelFor(target); - if (modelFor(panel).ready) - emit(panel, "show"); -}); - -on(hides, "data", ({target}) => { - let panel = panelFor(target); - if (modelFor(panel).ready) - emit(panel, "hide"); -}); - -on(ready, "data", ({target}) => { - let panel = panelFor(target); - let window = domPanel.getContentDocument(target).defaultView; - - workerFor(panel).attach(window); -}); - -on(readyToShow, "data", ({target}) => { - let panel = panelFor(target); - - if (!modelFor(panel).ready) { - modelFor(panel).ready = true; - - if (viewFor(panel).state == "open") - emit(panel, "show"); - } -}); - -on(start, "data", ({target}) => { - let panel = panelFor(target); - let window = domPanel.getContentDocument(target).defaultView; - - attach(styleFor(panel), window); -}); diff --git a/addon-sdk/source/lib/sdk/panel/events.js b/addon-sdk/source/lib/sdk/panel/events.js deleted file mode 100644 index f3040a11d..000000000 --- a/addon-sdk/source/lib/sdk/panel/events.js +++ /dev/null @@ -1,27 +0,0 @@ -/* 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"; - -// This module basically translates system/events to a SDK standard events -// so that `map`, `filter` and other utilities could be used with them. - -module.metadata = { - "stability": "experimental" -}; - -const events = require("../system/events"); -const { emit } = require("../event/core"); - -var channel = {}; - -function forward({ subject, type, data }) { - return emit(channel, "data", { target: subject, type: type, data: data }); -} - -["popupshowing", "popuphiding", "popupshown", "popuphidden", -"document-element-inserted", "DOMContentLoaded", "load" -].forEach(type => events.on(type, forward)); - -exports.events = channel; diff --git a/addon-sdk/source/lib/sdk/panel/utils.js b/addon-sdk/source/lib/sdk/panel/utils.js deleted file mode 100644 index c85b274bc..000000000 --- a/addon-sdk/source/lib/sdk/panel/utils.js +++ /dev/null @@ -1,451 +0,0 @@ -/* 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 { Cc, Ci } = require("chrome"); -const { Services } = require("resource://gre/modules/Services.jsm"); -const { setTimeout } = require("../timers"); -const { platform } = require("../system"); -const { getMostRecentBrowserWindow, getOwnerBrowserWindow, - getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils"); - -const { create: createFrame, swapFrameLoaders, getDocShell } = require("../frame/utils"); -const { window: addonWindow } = require("../addon/window"); -const { isNil } = require("../lang/type"); -const { data } = require('../self'); - -const events = require("../system/events"); - - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) { - position = position || {}; - - let x, y; - - let hasTop = !isNil(position.top); - let hasRight = !isNil(position.right); - let hasBottom = !isNil(position.bottom); - let hasLeft = !isNil(position.left); - let hasWidth = !isNil(width); - let hasHeight = !isNil(height); - - // if width is not specified by constructor or show's options, then get - // the default width - if (!hasWidth) - width = defaultWidth; - - // if height is not specified by constructor or show's options, then get - // the default height - if (!hasHeight) - height = defaultHeight; - - // default position is centered - x = (rect.right - width) / 2; - y = (rect.top + rect.bottom - height) / 2; - - if (hasTop) { - y = rect.top + position.top; - - if (hasBottom && !hasHeight) - height = rect.bottom - position.bottom - y; - } - else if (hasBottom) { - y = rect.bottom - position.bottom - height; - } - - if (hasLeft) { - x = position.left; - - if (hasRight && !hasWidth) - width = rect.right - position.right - x; - } - else if (hasRight) { - x = rect.right - width - position.right; - } - - return {x: x, y: y, width: width, height: height}; -} - -function open(panel, options, anchor) { - // Wait for the XBL binding to be constructed - if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor); - else display(panel, options, anchor); -} -exports.open = open; - -function isOpen(panel) { - return panel.state === "open" -} -exports.isOpen = isOpen; - -function isOpening(panel) { - return panel.state === "showing" -} -exports.isOpening = isOpening - -function close(panel) { - // Sometimes "TypeError: panel.hidePopup is not a function" is thrown - // when quitting the host application while a panel is visible. To suppress - // these errors, check for "hidePopup" in panel before calling it. - // It's not clear if there's an issue or it's expected behavior. - // See Bug 1151796. - - return panel.hidePopup && panel.hidePopup(); -} -exports.close = close - - -function resize(panel, width, height) { - // Resize the iframe instead of using panel.sizeTo - // because sizeTo doesn't work with arrow panels - if (panel.firstChild) { - panel.firstChild.style.width = width + "px"; - panel.firstChild.style.height = height + "px"; - } -} -exports.resize = resize - -function display(panel, options, anchor) { - let document = panel.ownerDocument; - - let x, y; - let { width, height, defaultWidth, defaultHeight } = options; - - let popupPosition = null; - - // Panel XBL has some SDK incompatible styling decisions. We shim panel - // instances until proper fix for Bug 859504 is shipped. - shimDefaultStyle(panel); - - if (!anchor) { - // The XUL Panel doesn't have an arrow, so the margin needs to be reset - // in order to, be positioned properly - panel.style.margin = "0"; - - let viewportRect = document.defaultView.gBrowser.getBoundingClientRect(); - - ({x, y, width, height} = calculateRegion(options, viewportRect)); - } - else { - // The XUL Panel has an arrow, so the margin needs to be reset - // to the default value. - panel.style.margin = ""; - let { CustomizableUI, window } = anchor.ownerDocument.defaultView; - - // In Australis, widgets may be positioned in an overflow panel or the - // menu panel. - // In such cases clicking this widget will hide the overflow/menu panel, - // and the widget's panel will show instead. - // If `CustomizableUI` is not available, it means the anchor is not in a - // chrome browser window, and therefore there is no need for this check. - if (CustomizableUI) { - let node = anchor; - ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window)); - - // if `node` is not the `anchor` itself, it means the widget is - // positioned in a panel, therefore we have to hide it before show - // the widget's panel in the same anchor - if (node !== anchor) - CustomizableUI.hidePanelForNode(anchor); - } - - width = width || defaultWidth; - height = height || defaultHeight; - - // Open the popup by the anchor. - let rect = anchor.getBoundingClientRect(); - - let zoom = getScreenPixelsPerCSSPixel(window); - let screenX = rect.left + window.mozInnerScreenX * zoom; - let screenY = rect.top + window.mozInnerScreenY * zoom; - - // Set up the vertical position of the popup relative to the anchor - // (always display the arrow on anchor center) - let horizontal, vertical; - if (screenY > window.screen.availHeight / 2 + height) - vertical = "top"; - else - vertical = "bottom"; - - if (screenY > window.screen.availWidth / 2 + width) - horizontal = "left"; - else - horizontal = "right"; - - let verticalInverse = vertical == "top" ? "bottom" : "top"; - popupPosition = vertical + "center " + verticalInverse + horizontal; - - // Allow panel to flip itself if the panel can't be displayed at the - // specified position (useful if we compute a bad position or if the - // user moves the window and panel remains visible) - panel.setAttribute("flip", "both"); - } - - if (!panel.viewFrame) { - panel.viewFrame = document.importNode(panel.backgroundFrame, false); - panel.appendChild(panel.viewFrame); - - let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes(); - let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId}); - getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal); - } - - // Resize the iframe instead of using panel.sizeTo - // because sizeTo doesn't work with arrow panels - panel.firstChild.style.width = width + "px"; - panel.firstChild.style.height = height + "px"; - - panel.openPopup(anchor, popupPosition, x, y); -} -exports.display = display; - -// This utility function is just a workaround until Bug 859504 has shipped. -function shimDefaultStyle(panel) { - let document = panel.ownerDocument; - // Please note that `panel` needs to be part of document in order to reach - // it's anonymous nodes. One of the anonymous node has a big padding which - // doesn't work well since panel frame needs to fill all of the panel. - // XBL binding is a not the best option as it's applied asynchronously, and - // makes injected frames behave in strange way. Also this feels a lot - // cheaper to do. - ["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) { - let node = document.getAnonymousElementByAttribute(panel, "class", value); - if (node) node.style.padding = 0; - }); -} - -function show(panel, options, anchor) { - // Prevent the panel from getting focus when showing up - // if focus is set to false - panel.setAttribute("noautofocus", !options.focus); - - let window = anchor && getOwnerBrowserWindow(anchor); - let { document } = window ? window : getMostRecentBrowserWindow(); - attach(panel, document); - - open(panel, options, anchor); -} -exports.show = show - -function onPanelClick(event) { - let { target, metaKey, ctrlKey, shiftKey, button } = event; - let accel = platform === "darwin" ? metaKey : ctrlKey; - let isLeftClick = button === 0; - let isMiddleClick = button === 1; - - if ((isLeftClick && (accel || shiftKey)) || isMiddleClick) { - let link = target.closest('a'); - - if (link && link.href) - getMostRecentBrowserWindow().openUILink(link.href, event) - } -} - -function setupPanelFrame(frame) { - frame.setAttribute("flex", 1); - frame.setAttribute("transparent", "transparent"); - frame.setAttribute("autocompleteenabled", true); - frame.setAttribute("tooltip", "aHTMLTooltip"); - if (platform === "darwin") { - frame.style.borderRadius = "var(--arrowpanel-border-radius, 3.5px)"; - frame.style.padding = "1px"; - } -} - -function make(document, options) { - document = document || getMostRecentBrowserWindow().document; - let panel = document.createElementNS(XUL_NS, "panel"); - panel.setAttribute("type", "arrow"); - panel.setAttribute("sdkscriptenabled", options.allowJavascript); - - // The panel needs to be attached to a browser window in order for us - // to copy browser styles to the content document when it loads. - attach(panel, document); - - let frameOptions = { - allowJavascript: options.allowJavascript, - allowPlugins: true, - allowAuth: true, - allowWindowControl: false, - // Need to override `nodeName` to use `iframe` as `browsers` save session - // history and in consequence do not dispatch "inner-window-destroyed" - // notifications. - browser: false, - }; - - let backgroundFrame = createFrame(addonWindow, frameOptions); - setupPanelFrame(backgroundFrame); - - getDocShell(backgroundFrame).inheritPrivateBrowsingId = false; - - function onPopupShowing({type, target}) { - if (target === this) { - let attrs = getDocShell(backgroundFrame).getOriginAttributes(); - getDocShell(panel.viewFrame).setOriginAttributes(attrs); - - swapFrameLoaders(backgroundFrame, panel.viewFrame); - } - } - - function onPopupHiding({type, target}) { - if (target === this) { - swapFrameLoaders(backgroundFrame, panel.viewFrame); - - panel.viewFrame.remove(); - panel.viewFrame = null; - } - } - - function onContentReady({target, type}) { - if (target === getContentDocument(panel)) { - style(panel); - events.emit(type, { subject: panel }); - } - } - - function onContentLoad({target, type}) { - if (target === getContentDocument(panel)) - events.emit(type, { subject: panel }); - } - - function onContentChange({subject: document, type}) { - if (document === getContentDocument(panel) && document.defaultView) - events.emit(type, { subject: panel }); - } - - function onPanelStateChange({target, type}) { - if (target === this) - events.emit(type, { subject: panel }) - } - - panel.addEventListener("popupshowing", onPopupShowing); - panel.addEventListener("popuphiding", onPopupHiding); - for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"]) - panel.addEventListener(event, onPanelStateChange); - - panel.addEventListener("click", onPanelClick, false); - - // Panel content document can be either in panel `viewFrame` or in - // a `backgroundFrame` depending on panel state. Listeners are set - // on both to avoid setting and removing listeners on panel state changes. - - panel.addEventListener("DOMContentLoaded", onContentReady, true); - backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true); - - panel.addEventListener("load", onContentLoad, true); - backgroundFrame.addEventListener("load", onContentLoad, true); - - events.on("document-element-inserted", onContentChange); - - panel.backgroundFrame = backgroundFrame; - panel.viewFrame = null; - - // Store event listener on the panel instance so that it won't be GC-ed - // while panel is alive. - panel.onContentChange = onContentChange; - - return panel; -} -exports.make = make; - -function attach(panel, document) { - document = document || getMostRecentBrowserWindow().document; - let container = document.getElementById("mainPopupSet"); - if (container !== panel.parentNode) { - detach(panel); - document.getElementById("mainPopupSet").appendChild(panel); - } -} -exports.attach = attach; - -function detach(panel) { - if (panel.parentNode) panel.parentNode.removeChild(panel); -} -exports.detach = detach; - -function dispose(panel) { - panel.backgroundFrame.remove(); - panel.backgroundFrame = null; - events.off("document-element-inserted", panel.onContentChange); - panel.onContentChange = null; - detach(panel); -} -exports.dispose = dispose; - -function style(panel) { - /** - Injects default OS specific panel styles into content document that is loaded - into given panel. Optionally `document` of the browser window can be - given to inherit styles from it, by default it will use either panel owner - document or an active browser's document. It should not matter though unless - Firefox decides to style windows differently base on profile or mode like - chrome for example. - **/ - - try { - let document = panel.ownerDocument; - let contentDocument = getContentDocument(panel); - let window = document.defaultView; - let node = document.getAnonymousElementByAttribute(panel, "class", - "panel-arrowcontent"); - - let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node); - - let style = contentDocument.createElement("style"); - style.id = "sdk-panel-style"; - style.textContent = "body { " + - "color: " + color + ";" + - "font-family: " + fontFamily + ";" + - "font-weight: " + fontWeight + ";" + - "font-size: " + fontSize + ";" + - "}"; - - let container = contentDocument.head ? contentDocument.head : - contentDocument.documentElement; - - if (container.firstChild) - container.insertBefore(style, container.firstChild); - else - container.appendChild(style); - } - catch (error) { - console.error("Unable to apply panel style"); - console.exception(error); - } -} -exports.style = style; - -var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame; -exports.getContentFrame = getContentFrame; - -function getContentDocument(panel) { - return getContentFrame(panel).contentDocument; -} -exports.getContentDocument = getContentDocument; - -function setURL(panel, url) { - let frame = getContentFrame(panel); - let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation); - - webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null); -} - -exports.setURL = setURL; - -function allowContextMenu(panel, allow) { - if (allow) { - panel.setAttribute("context", "contentAreaContextMenu"); - } - else { - panel.removeAttribute("context"); - } -} -exports.allowContextMenu = allowContextMenu; diff --git a/addon-sdk/source/lib/sdk/passwords.js b/addon-sdk/source/lib/sdk/passwords.js deleted file mode 100644 index 70f0aa4da..000000000 --- a/addon-sdk/source/lib/sdk/passwords.js +++ /dev/null @@ -1,61 +0,0 @@ -/* 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": "stable" -}; - -const { search, remove, store } = require("./passwords/utils"); -const { defer, delay } = require("./lang/functional"); - -/** - * Utility function that returns `onComplete` and `onError` callbacks form the - * given `options` objects. Also properties are removed from the passed - * `options` objects. - * @param {Object} options - * Object that is passed to the exported functions of this module. - * @returns {Function[]} - * Array with two elements `onComplete` and `onError` functions. - */ -function getCallbacks(options) { - let value = [ - 'onComplete' in options ? options.onComplete : null, - 'onError' in options ? defer(options.onError) : console.exception - ]; - - delete options.onComplete; - delete options.onError; - - return value; -}; - -/** - * Creates a wrapper function that tries to call `onComplete` with a return - * value of the wrapped function or falls back to `onError` if wrapped function - * throws an exception. - */ -function createWrapperMethod(wrapped) { - return function (options) { - let [ onComplete, onError ] = getCallbacks(options); - try { - let value = wrapped(options); - if (onComplete) { - delay(function() { - try { - onComplete(value); - } catch (exception) { - onError(exception); - } - }); - } - } catch (exception) { - onError(exception); - } - }; -} - -exports.search = createWrapperMethod(search); -exports.store = createWrapperMethod(store); -exports.remove = createWrapperMethod(remove); diff --git a/addon-sdk/source/lib/sdk/passwords/utils.js b/addon-sdk/source/lib/sdk/passwords/utils.js deleted file mode 100644 index 334efa490..000000000 --- a/addon-sdk/source/lib/sdk/passwords/utils.js +++ /dev/null @@ -1,107 +0,0 @@ -/* 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 { Cc, Ci, CC } = require("chrome"); -const { uri: ADDON_URI } = require("../self"); -const loginManager = Cc["@mozilla.org/login-manager;1"]. - getService(Ci.nsILoginManager); -const { URL: parseURL } = require("../url"); -const LoginInfo = CC("@mozilla.org/login-manager/loginInfo;1", - "nsILoginInfo", "init"); - -function filterMatchingLogins(loginInfo) { - return Object.keys(this).every(key => loginInfo[key] === this[key], this); -} - -/** - * Removes `user`, `password` and `path` fields from the given `url` if it's - * 'http', 'https' or 'ftp'. All other URLs are returned unchanged. - * @example - * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com - */ -function normalizeURL(url) { - let { scheme, host, port } = parseURL(url); - // We normalize URL only if it's `http`, `https` or `ftp`. All other types of - // URLs (`resource`, `chrome`, etc..) should not be normalized as they are - // used with add-on associated credentials path. - return scheme === "http" || scheme === "https" || scheme === "ftp" ? - scheme + "://" + (host || "") + (port ? ":" + port : "") : - url -} - -function Login(options) { - let login = Object.create(Login.prototype); - Object.keys(options || {}).forEach(function(key) { - if (key === 'url') - login.hostname = normalizeURL(options.url); - else if (key === 'formSubmitURL') - login.formSubmitURL = options.formSubmitURL ? - normalizeURL(options.formSubmitURL) : null; - else if (key === 'realm') - login.httpRealm = options.realm; - else - login[key] = options[key]; - }); - - return login; -} -Login.prototype.toJSON = function toJSON() { - return { - url: this.hostname || ADDON_URI, - realm: this.httpRealm || null, - formSubmitURL: this.formSubmitURL || null, - username: this.username || null, - password: this.password || null, - usernameField: this.usernameField || '', - passwordField: this.passwordField || '', - } -}; -Login.prototype.toLoginInfo = function toLoginInfo() { - let { url, realm, formSubmitURL, username, password, usernameField, - passwordField } = this.toJSON(); - - return new LoginInfo(url, formSubmitURL, realm, username, password, - usernameField, passwordField); -}; - -function loginToJSON(value) { - return Login(value).toJSON(); -} - -/** - * Returns array of `nsILoginInfo` objects that are stored in the login manager - * and have all the properties with matching values as a given `options` object. - * @param {Object} options - * @returns {nsILoginInfo[]} - */ -exports.search = function search(options) { - return loginManager.getAllLogins() - .filter(filterMatchingLogins, Login(options)) - .map(loginToJSON); -}; - -/** - * Stores login info created from the given `options` to the applications - * built-in login management system. - * @param {Object} options. - */ -exports.store = function store(options) { - loginManager.addLogin(Login(options).toLoginInfo()); -}; - -/** - * Removes login info from the applications built-in login management system. - * _Please note: When removing a login info the specified properties must - * exactly match to the one that is already stored or exception will be thrown._ - * @param {Object} options. - */ -exports.remove = function remove(options) { - loginManager.removeLogin(Login(options).toLoginInfo()); -}; diff --git a/addon-sdk/source/lib/sdk/places/bookmarks.js b/addon-sdk/source/lib/sdk/places/bookmarks.js deleted file mode 100644 index c4f9528f1..000000000 --- a/addon-sdk/source/lib/sdk/places/bookmarks.js +++ /dev/null @@ -1,395 +0,0 @@ -/* 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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { send } = require('../addon/events'); -const { defer, reject, all, resolve, promised } = require('../core/promise'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { identity, defer:async } = require('../lang/functional'); -const { extend, merge } = require('../util/object'); -const { fromIterator } = require('../util/array'); -const { - constructTree, fetchItem, createQuery, - isRootGroup, createQueryOptions -} = require('./utils'); -const { - bookmarkContract, groupContract, separatorContract -} = require('./contract'); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - -/* - * Mapping of uncreated bookmarks with their created - * counterparts - */ -const itemMap = new WeakMap(); - -/* - * Constant used by nsIHistoryQuery; 1 is a bookmark query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const BOOKMARK_QUERY = 1; - -/* - * Bookmark Item classes - */ - -const Bookmark = Class({ - extends: [ - bookmarkContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, bookmarkContract(extend(defaults, options))); - }, - type: 'bookmark', - toString: () => '[object Bookmark]' -}); -exports.Bookmark = Bookmark; - -const Group = Class({ - extends: [ - groupContract.properties(identity) - ], - initialize: function initialize (options) { - // Don't validate if root group - if (isRootGroup(options)) - merge(this, options); - else - merge(this, groupContract(extend(defaults, options))); - }, - type: 'group', - toString: () => '[object Group]' -}); -exports.Group = Group; - -const Separator = Class({ - extends: [ - separatorContract.properties(identity) - ], - initialize: function initialize (options) { - merge(this, separatorContract(extend(defaults, options))); - }, - type: 'separator', - toString: () => '[object Separator]' -}); -exports.Separator = Separator; - -/* - * Functions - */ - -function save (items, options) { - items = [].concat(items); - options = options || {}; - let emitter = EventTarget(); - let results = []; - let errors = []; - let root = constructTree(items); - let cache = new Map(); - - let isExplicitSave = item => !!~items.indexOf(item); - // `walk` returns an aggregate promise indicating the completion - // of the `commitItem` on each node, not whether or not that - // commit was successful - - // Force this to be async, as if a ducktype fails validation, - // the promise implementation will fire an error event, which will - // not trigger the handler as it's not yet bound - // - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => root.walk(preCommitItem).then(commitComplete))(); - - function preCommitItem ({value:item}) { - // Do nothing if tree root, default group (unsavable), - // or if it's a dependency and not explicitly saved (in the list - // of items to be saved), and not needed to be saved - if (item === null || // node is the tree root - isRootGroup(item) || - (getId(item) && !isExplicitSave(item))) - return; - - return promised(validate)(item) - .then(() => commitItem(item, options)) - .then(data => construct(data, cache)) - .then(savedItem => { - // If item was just created, make a map between - // the creation object and created object, - // so we can reference the item that doesn't have an id - if (!getId(item)) - saveId(item, savedItem.id); - - // Emit both the processed item, and original item - // so a mapping can be understood in handler - emit(emitter, 'data', savedItem, item); - - // Push to results iff item was explicitly saved - if (isExplicitSave(item)) - results[items.indexOf(item)] = savedItem; - }, reason => { - // Force reason to be a string for consistency - reason = reason + ''; - // Emit both the reason, and original item - // so a mapping can be understood in handler - emit(emitter, 'error', reason + '', item); - // Store unsaved item in results list - results[items.indexOf(item)] = item; - errors.push(reason); - }); - } - - // Called when traversal of the node tree is completed and all - // items have been committed - function commitComplete () { - emit(emitter, 'end', results); - } - - return emitter; -} -exports.save = save; - -function search (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let cache = new Map(); - let queryObjs = queries.map(createQuery.bind(null, BOOKMARK_QUERY)); - let optionsObj = createQueryOptions(BOOKMARK_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { queries: queryObjs, options: optionsObj }) - .then(handleQueryResponse); - })(); - - function handleQueryResponse (data) { - let deferreds = data.map(item => { - return construct(item, cache).then(bookmark => { - emit(emitter, 'data', bookmark); - return bookmark; - }, reason => { - emit(emitter, 'error', reason); - errors.push(reason); - }); - }); - - all(deferreds).then(data => { - emit(emitter, 'end', data); - }, () => emit(emitter, 'end', [])); - } - - return emitter; -} -exports.search = search; - -function remove (items) { - return [].concat(items).map(item => { - item.remove = true; - return item; - }); -} - -exports.remove = remove; - -/* - * Internal Utilities - */ - -function commitItem (item, options) { - // Get the item's ID, or getId it's saved version if it exists - let id = getId(item); - let data = normalize(item); - let promise; - - data.id = id; - - if (!id) { - promise = send('sdk-places-bookmarks-create', data); - } else if (item.remove) { - promise = send('sdk-places-bookmarks-remove', { id: id }); - } else { - promise = send('sdk-places-bookmarks-last-updated', { - id: id - }).then(function (updated) { - // If attempting to save an item that is not the - // latest snapshot of a bookmark item, execute - // the resolution function - if (updated !== item.updated && options.resolve) - return fetchItem(id) - .then(options.resolve.bind(null, data)); - else - return data; - }).then(send.bind(null, 'sdk-places-bookmarks-save')); - } - - return promise; -} - -/* - * Turns a bookmark item into a plain object, - * converts `tags` from Set to Array, group instance to an id - */ -function normalize (item) { - let data = merge({}, item); - // Circumvent prototype property of `type` - delete data.type; - data.type = item.type; - data.tags = []; - if (item.tags) { - data.tags = fromIterator(item.tags); - } - data.group = getId(data.group) || exports.UNSORTED.id; - - return data; -} - -/* - * Takes a data object and constructs a BookmarkItem instance - * of it, recursively generating parent instances as well. - * - * Pass in a `cache` Map to reuse instances of - * bookmark items to reduce overhead; - * The cache object is a map of id to a deferred with a - * promise that resolves to the bookmark item. - */ -function construct (object, cache, forced) { - let item = instantiate(object); - let deferred = defer(); - - // Item could not be instantiated - if (!item) - return resolve(null); - - // Return promise for item if found in the cache, - // and not `forced`. `forced` indicates that this is the construct - // call that should not read from cache, but should actually perform - // the construction, as it was set before several async calls - if (cache.has(item.id) && !forced) - return cache.get(item.id).promise; - else if (cache.has(item.id)) - deferred = cache.get(item.id); - else - cache.set(item.id, deferred); - - // When parent group is found in cache, use - // the same deferred value - if (item.group && cache.has(item.group)) { - cache.get(item.group).promise.then(group => { - item.group = group; - deferred.resolve(item); - }); - - // If not in the cache, and a root group, return - // the premade instance - } else if (rootGroups.get(item.group)) { - item.group = rootGroups.get(item.group); - deferred.resolve(item); - - // If not in the cache or a root group, fetch the parent - } else { - cache.set(item.group, defer()); - fetchItem(item.group).then(group => { - return construct(group, cache, true); - }).then(group => { - item.group = group; - deferred.resolve(item); - }, deferred.reject); - } - - return deferred.promise; -} - -function instantiate (object) { - if (object.type === 'bookmark') - return Bookmark(object); - if (object.type === 'group') - return Group(object); - if (object.type === 'separator') - return Separator(object); - return null; -} - -/** - * Validates a bookmark item; will throw an error if ininvalid, - * to be used with `promised`. As bookmark items check on their class, - * this only checks ducktypes - */ -function validate (object) { - if (!isDuckType(object)) return true; - let contract = object.type === 'bookmark' ? bookmarkContract : - object.type === 'group' ? groupContract : - object.type === 'separator' ? separatorContract : - null; - if (!contract) { - throw Error('No type specified'); - } - - // If object has a property set, and undefined, - // manually override with default as it'll fail otherwise - let withDefaults = Object.keys(defaults).reduce((obj, prop) => { - if (obj[prop] == null) obj[prop] = defaults[prop]; - return obj; - }, extend(object)); - - contract(withDefaults); -} - -function isDuckType (item) { - return !(item instanceof Bookmark) && - !(item instanceof Group) && - !(item instanceof Separator); -} - -function saveId (unsaved, id) { - itemMap.set(unsaved, id); -} - -// Fetches an item's ID from itself, or from the mapped items -function getId (item) { - return typeof item === 'number' ? item : - item ? item.id || itemMap.get(item) : - null; -} - -/* - * Set up the default, root groups - */ - -var defaultGroupMap = { - MENU: bmsrv.bookmarksMenuFolder, - TOOLBAR: bmsrv.toolbarFolder, - UNSORTED: bmsrv.unfiledBookmarksFolder -}; - -var rootGroups = new Map(); - -for (let i in defaultGroupMap) { - let group = Object.freeze(Group({ title: i, id: defaultGroupMap[i] })); - rootGroups.set(defaultGroupMap[i], group); - exports[i] = group; -} - -var defaults = { - group: exports.UNSORTED, - index: -1 -}; diff --git a/addon-sdk/source/lib/sdk/places/contract.js b/addon-sdk/source/lib/sdk/places/contract.js deleted file mode 100644 index a3541c34d..000000000 --- a/addon-sdk/source/lib/sdk/places/contract.js +++ /dev/null @@ -1,73 +0,0 @@ -/* 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 { Cc, Ci } = require('chrome'); -const { isValidURI, URL } = require('../url'); -const { contract } = require('../util/contract'); -const { extend } = require('../util/object'); - -// map of property validations -const validItem = { - id: { - is: ['number', 'undefined', 'null'], - }, - group: { - is: ['object', 'number', 'undefined', 'null'], - ok: function (value) { - return value && - (value.toString && value.toString() === '[object Group]') || - typeof value === 'number' || - value.type === 'group'; - }, - msg: 'The `group` property must be a valid Group object' - }, - index: { - is: ['undefined', 'null', 'number'], - map: value => value == null ? -1 : value, - msg: 'The `index` property must be a number.' - }, - updated: { - is: ['number', 'undefined'] - } -}; - -const validTitle = { - title: { - is: ['string'], - msg: 'The `title` property must be defined.' - } -}; - -const validURL = { - url: { - is: ['string'], - ok: isValidURI, - msg: 'The `url` property must be a valid URL.' - } -}; - -const validTags = { - tags: { - is: ['object'], - ok: tags => tags instanceof Set, - map: function (tags) { - if (Array.isArray(tags)) - return new Set(tags); - if (tags == null) - return new Set(); - return tags; - }, - msg: 'The `tags` property must be a Set, or an array' - } -}; - -exports.bookmarkContract = contract( - extend(validItem, validTitle, validURL, validTags)); -exports.separatorContract = contract(validItem); -exports.groupContract = contract(extend(validItem, validTitle)); diff --git a/addon-sdk/source/lib/sdk/places/events.js b/addon-sdk/source/lib/sdk/places/events.js deleted file mode 100644 index a3f95ee03..000000000 --- a/addon-sdk/source/lib/sdk/places/events.js +++ /dev/null @@ -1,128 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '*', - "SeaMonkey": '*' - } -}; - -const { Cc, Ci } = require('chrome'); -const { Unknown } = require('../platform/xpcom'); -const { Class } = require('../core/heritage'); -const { merge } = require('../util/object'); -const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const { mapBookmarkItemType } = require('./utils'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { when } = require('../system/unload'); - -const emitter = EventTarget(); - -var HISTORY_ARGS = { - onBeginUpdateBatch: [], - onEndUpdateBatch: [], - onClearHistory: [], - onDeleteURI: ['url'], - onDeleteVisits: ['url', 'visitTime'], - onPageChanged: ['url', 'property', 'value'], - onTitleChanged: ['url', 'title'], - onVisit: [ - 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType' - ] -}; - -var HISTORY_EVENTS = { - onBeginUpdateBatch: 'history-start-batch', - onEndUpdateBatch: 'history-end-batch', - onClearHistory: 'history-start-clear', - onDeleteURI: 'history-delete-url', - onDeleteVisits: 'history-delete-visits', - onPageChanged: 'history-page-changed', - onTitleChanged: 'history-title-changed', - onVisit: 'history-visit' -}; - -var BOOKMARK_ARGS = { - onItemAdded: [ - 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded' - ], - onItemChanged: [ - 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId' - ], - onItemMoved: [ - 'id', 'previousParentId', 'previousIndex', 'currentParentId', - 'currentIndex', 'type' - ], - onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'], - onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId'] -}; - -var BOOKMARK_EVENTS = { - onItemAdded: 'bookmark-item-added', - onItemChanged: 'bookmark-item-changed', - onItemMoved: 'bookmark-item-moved', - onItemRemoved: 'bookmark-item-removed', - onItemVisited: 'bookmark-item-visited', -}; - -function createHandler (type, propNames) { - propNames = propNames || []; - return function (...args) { - let data = propNames.reduce((acc, prop, i) => { - if (prop) - acc[prop] = formatValue(prop, args[i]); - return acc; - }, {}); - - emit(emitter, 'data', { - type: type, - data: data - }); - }; -} - -/* - * Creates an observer, creating handlers based off of - * the `events` names, and ordering arguments from `propNames` hash - */ -function createObserverInstance (events, propNames) { - let definition = Object.keys(events).reduce((prototype, eventName) => { - prototype[eventName] = createHandler(events[eventName], propNames[eventName]); - return prototype; - }, {}); - - return Class(merge(definition, { extends: Unknown }))(); -} - -/* - * Formats `data` based off of the value of `type` - */ -function formatValue (type, data) { - if (type === 'type') - return mapBookmarkItemType(data); - if (type === 'url' && data) - return data.spec; - return data; -} - -var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS); -historyService.addObserver(historyObserver, false); - -var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS); -bookmarkService.addObserver(bookmarkObserver, false); - -when(() => { - historyService.removeObserver(historyObserver); - bookmarkService.removeObserver(bookmarkObserver); -}); - -exports.events = emitter; diff --git a/addon-sdk/source/lib/sdk/places/favicon.js b/addon-sdk/source/lib/sdk/places/favicon.js deleted file mode 100644 index 05b057db1..000000000 --- a/addon-sdk/source/lib/sdk/places/favicon.js +++ /dev/null @@ -1,49 +0,0 @@ -/* 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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { defer, reject } = require("../core/promise"); -const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. - getService(Ci.nsIFaviconService); -const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons); -const { isValidURI } = require("../url"); -const { newURI, getURL } = require("../url/utils"); - -/** - * Takes an object of several possible types and - * returns a promise that resolves to the page's favicon URI. - * @param {String|Tab} object - * @param {Function} (callback) - * @returns {Promise} - */ - -function getFavicon (object, callback) { - let url = getURL(object); - let deferred = defer(); - - if (url && isValidURI(url)) { - AsyncFavicons.getFaviconURLForPage(newURI(url), function (aURI) { - if (aURI && aURI.spec) - deferred.resolve(aURI.spec.toString()); - else - deferred.reject(null); - }); - } else { - deferred.reject(null); - } - - if (callback) deferred.promise.then(callback, callback); - return deferred.promise; -} -exports.getFavicon = getFavicon; diff --git a/addon-sdk/source/lib/sdk/places/history.js b/addon-sdk/source/lib/sdk/places/history.js deleted file mode 100644 index b243b024c..000000000 --- a/addon-sdk/source/lib/sdk/places/history.js +++ /dev/null @@ -1,65 +0,0 @@ -/* 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", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -/* - * Requiring hosts so they can subscribe to client messages - */ -require('./host/host-bookmarks'); -require('./host/host-tags'); -require('./host/host-query'); - -const { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { events, send } = require('../addon/events'); -const { defer, reject, all } = require('../core/promise'); -const { uuid } = require('../util/uuid'); -const { flatten } = require('../util/array'); -const { has, extend, merge, pick } = require('../util/object'); -const { emit } = require('../event/core'); -const { defer: async } = require('../lang/functional'); -const { EventTarget } = require('../event/target'); -const { - urlQueryParser, createQuery, createQueryOptions -} = require('./utils'); - -/* - * Constant used by nsIHistoryQuery; 0 is a history query - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - */ -const HISTORY_QUERY = 0; - -var search = function query (queries, options) { - queries = [].concat(queries); - let emitter = EventTarget(); - let queryObjs = queries.map(createQuery.bind(null, HISTORY_QUERY)); - let optionsObj = createQueryOptions(HISTORY_QUERY, options); - - // Can remove after `Promise.jsm` is implemented in Bug 881047, - // which will guarantee next tick execution - async(() => { - send('sdk-places-query', { - query: queryObjs, - options: optionsObj - }).then(results => { - results.map(item => emit(emitter, 'data', item)); - emit(emitter, 'end', results); - }, reason => { - emit(emitter, 'error', reason); - emit(emitter, 'end', []); - }); - })(); - - return emitter; -}; -exports.search = search; diff --git a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js b/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js deleted file mode 100644 index 3245c4070..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-bookmarks.js +++ /dev/null @@ -1,238 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"]. - getService(Ci.nsIBrowserHistory); -const asyncHistory = Cc["@mozilla.org/browser/history;1"]. - getService(Ci.mozIAsyncHistory); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { query } = require('./host-query'); -const { - defer, all, resolve, promised, reject -} = require('../../core/promise'); -const { request, response } = require('../../addon/host'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); -const { URL, isValidURI } = require('../../url'); -const { newURI } = require('../../url/utils'); - -const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX; -const UNSORTED_ID = bmsrv.unfiledBookmarksFolder; -const ROOT_FOLDERS = [ - bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder, - bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-bookmarks-create': createBookmarkItem, - 'sdk-places-bookmarks-save': saveBookmarkItem, - 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated, - 'sdk-places-bookmarks-get': getBookmarkItem, - 'sdk-places-bookmarks-remove': removeBookmarkItem, - 'sdk-places-bookmarks-get-all': getAllBookmarks, - 'sdk-places-bookmarks-get-children': getChildren -}; - -function typeMap (type) { - if (typeof type === 'number') { - if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; - if (bmsrv.TYPE_FOLDER === type) return 'group'; - if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; - } else { - if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; - if ('group' === type) return bmsrv.TYPE_FOLDER; - if ('separator' === type) return bmsrv.TYPE_SEPARATOR; - } -} - -function getBookmarkLastUpdated ({id}) { - return resolve(bmsrv.getItemLastModified(id)); -} -exports.getBookmarkLastUpdated; - -function createBookmarkItem (data) { - let error; - - if (data.group == null) data.group = UNSORTED_ID; - if (data.index == null) data.index = DEFAULT_INDEX; - - if (data.type === 'group') - data.id = bmsrv.createFolder( - data.group, data.title, data.index - ); - else if (data.type === 'separator') - data.id = bmsrv.insertSeparator( - data.group, data.index - ); - else - data.id = bmsrv.insertBookmark( - data.group, newURI(data.url), data.index, data.title - ); - - // In the event where default or no index is provided (-1), - // query the actual index for the response - if (data.index === -1) - data.index = bmsrv.getItemIndex(data.id); - - try { - data.updated = bmsrv.getItemLastModified(data.id); - } - catch (e) { - console.exception(e); - } - - return tag(data, true).then(() => data); -} -exports.createBookmarkItem = createBookmarkItem; - -function saveBookmarkItem (data) { - let id = data.id; - if (!id) - reject('Item is missing id'); - - let group = bmsrv.getFolderIdForItem(id); - let index = bmsrv.getItemIndex(id); - let type = bmsrv.getItemType(id); - let title = typeMap(type) !== 'separator' ? - bmsrv.getItemTitle(id) : - undefined; - let url = typeMap(type) === 'bookmark' ? - bmsrv.getBookmarkURI(id).spec : - undefined; - - if (url != data.url) - bmsrv.changeBookmarkURI(id, newURI(data.url)); - else if (typeMap(type) === 'bookmark') - data.url = url; - - if (title != data.title) - bmsrv.setItemTitle(id, data.title); - else if (typeMap(type) !== 'separator') - data.title = title; - - if (data.group && data.group !== group) - bmsrv.moveItem(id, data.group, data.index || -1); - else if (data.index != null && data.index !== index) { - // We use moveItem here instead of setItemIndex - // so we don't have to manage the indicies of the siblings - bmsrv.moveItem(id, group, data.index); - } else if (data.index == null) - data.index = index; - - data.updated = bmsrv.getItemLastModified(data.id); - - return tag(data).then(() => data); -} -exports.saveBookmarkItem = saveBookmarkItem; - -function removeBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - bmsrv.removeItem(id); - return resolve(null); -} -exports.removeBookmarkItem = removeBookmarkItem; - -function getBookmarkItem (data) { - let id = data.id; - - if (!id) - reject('Item is missing id'); - - let type = bmsrv.getItemType(id); - - data.type = typeMap(type); - - if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER) - data.title = bmsrv.getItemTitle(id); - - if (type === bmsrv.TYPE_BOOKMARK) { - data.url = bmsrv.getBookmarkURI(id).spec; - // Should be moved into host-tags as a method - data.tags = taggingService.getTagsForURI(newURI(data.url), {}); - } - - data.group = bmsrv.getFolderIdForItem(id); - data.index = bmsrv.getItemIndex(id); - data.updated = bmsrv.getItemLastModified(data.id); - - return resolve(data); -} -exports.getBookmarkItem = getBookmarkItem; - -function getAllBookmarks () { - return query({}, { queryType: 1 }).then(bookmarks => - all(bookmarks.map(getBookmarkItem))); -} -exports.getAllBookmarks = getAllBookmarks; - -function getChildren ({ id }) { - if (typeMap(bmsrv.getItemType(id)) !== 'group') return []; - let ids = []; - for (let i = 0; ids[ids.length - 1] !== -1; i++) - ids.push(bmsrv.getIdForItemAt(id, i)); - ids.pop(); - return all(ids.map(id => getBookmarkItem({ id: id }))); -} -exports.getChildren = getChildren; - -/* - * Hook into host - */ - -var reqStream = filter(request, (data) => /sdk-places-bookmarks/.test(data.event)); -on(reqStream, 'data', ({ event, id, data }) => { - if (!EVENT_MAP[event]) return; - - let resData = { id: id, event: event }; - - promised(EVENT_MAP[event])(data). - then(res => resData.data = res, e => resData.error = e). - then(() => emit(response, 'data', resData)); -}); - -function tag (data, isNew) { - // If a new item, we can skip checking what other tags - // are on the item - if (data.type !== 'bookmark') { - return resolve(); - } - else if (!isNew) { - return send('sdk-places-tags-get-tags-by-url', { url: data.url }) - .then(tags => { - return send('sdk-places-tags-untag', { - tags: tags.filter(tag => !~data.tags.indexOf(tag)), - url: data.url - }); - }).then(() => send('sdk-places-tags-tag', { - url: data.url, tags: data.tags - })); - } - else if (data.tags && data.tags.length) { - return send('sdk-places-tags-tag', { url: data.url, tags: data.tags }); - } - else - return resolve(); -} - diff --git a/addon-sdk/source/lib/sdk/places/host/host-query.js b/addon-sdk/source/lib/sdk/places/host/host-query.js deleted file mode 100644 index f2dbd6550..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-query.js +++ /dev/null @@ -1,179 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const { all } = require('../../core/promise'); -const { safeMerge, omit } = require('../../util/object'); -const historyService = Cc['@mozilla.org/browser/nav-history-service;1'] - .getService(Ci.nsINavHistoryService); -const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1'] - .getService(Ci.nsINavBookmarksService); -const { request, response } = require('../../addon/host'); -const { newURI } = require('../../url/utils'); -const { send } = require('../../addon/events'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const ROOT_FOLDERS = [ - bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder, - bookmarksService.bookmarksMenuFolder -]; - -const EVENT_MAP = { - 'sdk-places-query': queryReceiver -}; - -// Properties that need to be manually -// copied into a nsINavHistoryQuery object -const MANUAL_QUERY_PROPERTIES = [ - 'uri', 'folder', 'tags', 'url', 'folder' -]; - -const PLACES_PROPERTIES = [ - 'uri', 'title', 'accessCount', 'time' -]; - -function execute (queries, options) { - return new Promise(resolve => { - let root = historyService - .executeQueries(queries, queries.length, options).root; - // Let's extract an eventual uri wildcard, if both domain and uri are set. - // See utils.js::urlQueryParser() for more details. - // In case of multiple queries, we only retain the first found wildcard. - let uriWildcard = queries.reduce((prev, query) => { - if (query.uri && query.domain) { - if (!prev) - prev = query.uri.spec; - query.uri = null; - } - return prev; - }, ""); - resolve(collect([], root, uriWildcard)); - }); -} - -function collect (acc, node, uriWildcard) { - node.containerOpen = true; - for (let i = 0; i < node.childCount; i++) { - let child = node.getChild(i); - - if (!uriWildcard || child.uri.startsWith(uriWildcard)) { - acc.push(child); - } - if (child.type === child.RESULT_TYPE_FOLDER) { - let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode); - collect(acc, container, uriWildcard); - } - } - node.containerOpen = false; - return acc; -} - -function query (queries, options) { - return new Promise((resolve, reject) => { - queries = queries || []; - options = options || {}; - let optionsObj, queryObjs; - - optionsObj = historyService.getNewQueryOptions(); - queryObjs = [].concat(queries).map(createQuery); - if (!queryObjs.length) { - queryObjs = [historyService.getNewQuery()]; - } - safeMerge(optionsObj, options); - - /* - * Currently `places:` queries are not supported - */ - optionsObj.excludeQueries = true; - - execute(queryObjs, optionsObj).then((results) => { - if (optionsObj.queryType === 0) { - return results.map(normalize); - } - else if (optionsObj.queryType === 1) { - // Formats query results into more standard - // data structures for returning - return all(results.map(({itemId}) => - send('sdk-places-bookmarks-get', { id: itemId }))); - } - }).then(resolve, reject); - }); -} -exports.query = query; - -function createQuery (query) { - query = query || {}; - let queryObj = historyService.getNewQuery(); - - safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES)); - - if (query.tags && Array.isArray(query.tags)) - queryObj.tags = query.tags; - if (query.uri || query.url) - queryObj.uri = newURI(query.uri || query.url); - if (query.folder) - queryObj.setFolders([query.folder], 1); - return queryObj; -} - -function queryReceiver (message) { - let queries = message.data.queries || message.data.query; - let options = message.data.options; - let resData = { - id: message.id, - event: message.event - }; - - query(queries, options).then(results => { - resData.data = results; - respond(resData); - }, reason => { - resData.error = reason; - respond(resData); - }); -} - -/* - * Converts a nsINavHistoryResultNode into a plain object - * - * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode - */ -function normalize (historyObj) { - return PLACES_PROPERTIES.reduce((obj, prop) => { - if (prop === 'uri') - obj.url = historyObj.uri; - else if (prop === 'time') { - // Cast from microseconds to milliseconds - obj.time = Math.floor(historyObj.time / 1000) - } - else if (prop === 'accessCount') - obj.visitCount = historyObj[prop]; - else - obj[prop] = historyObj[prop]; - return obj; - }, {}); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, data => /sdk-places-query/.test(data.event)); -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/host/host-tags.js b/addon-sdk/source/lib/sdk/places/host/host-tags.js deleted file mode 100644 index 929a5d5af..000000000 --- a/addon-sdk/source/lib/sdk/places/host/host-tags.js +++ /dev/null @@ -1,92 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci } = require('chrome'); -const taggingService = Cc["@mozilla.org/browser/tagging-service;1"]. - getService(Ci.nsITaggingService); -const ios = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const { URL } = require('../../url'); -const { newURI } = require('../../url/utils'); -const { request, response } = require('../../addon/host'); -const { on, emit } = require('../../event/core'); -const { filter } = require('../../event/utils'); - -const EVENT_MAP = { - 'sdk-places-tags-tag': tag, - 'sdk-places-tags-untag': untag, - 'sdk-places-tags-get-tags-by-url': getTagsByURL, - 'sdk-places-tags-get-urls-by-tag': getURLsByTag -}; - -function tag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.tagURI(newURI(data.url), data.tags); - respond(resData); -} - -function untag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.untagURI(newURI(data.url), data.tags); - respond(resData); -} - -function getURLsByTag (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService - .getURIsForTag(data.tag).map(uri => uri.spec); - respond(resData); -} - -function getTagsByURL (message) { - let data = message.data; - let resData = { - id: message.id, - event: message.event - }; - - resData.data = taggingService.getTagsForURI(newURI(data.url), {}); - respond(resData); -} - -/* - * Hook into host - */ - -var reqStream = filter(request, function (data) { - return /sdk-places-tags/.test(data.event); -}); - -on(reqStream, 'data', function (e) { - if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e); -}); - -function respond (data) { - emit(response, 'data', data); -} diff --git a/addon-sdk/source/lib/sdk/places/utils.js b/addon-sdk/source/lib/sdk/places/utils.js deleted file mode 100644 index 44366d2aa..000000000 --- a/addon-sdk/source/lib/sdk/places/utils.js +++ /dev/null @@ -1,268 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Cc, Ci, Cu } = require('chrome'); -const { Class } = require('../core/heritage'); -const { method } = require('../lang/functional'); -const { defer, promised, all } = require('../core/promise'); -const { send } = require('../addon/events'); -const { EventTarget } = require('../event/target'); -const { merge } = require('../util/object'); -const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - -Cu.importGlobalProperties(["URL"]); - -/* - * TreeNodes are used to construct dependency trees - * for BookmarkItems - */ -var TreeNode = Class({ - initialize: function (value) { - this.value = value; - this.children = []; - }, - add: function (values) { - [].concat(values).forEach(value => { - this.children.push(value instanceof TreeNode ? value : TreeNode(value)); - }); - }, - get length () { - let count = 0; - this.walk(() => count++); - // Do not count the current node - return --count; - }, - get: method(get), - walk: method(walk), - toString: () => '[object TreeNode]' -}); -exports.TreeNode = TreeNode; - -/* - * Descends down from `node` applying `fn` to each in order. - * `fn` can return values or promises -- if promise returned, - * children are not processed until resolved. `fn` is passed - * one argument, the current node, `curr`. - */ -function walk (curr, fn) { - return promised(fn)(curr).then(val => { - return all(curr.children.map(child => walk(child, fn))); - }); -} - -/* - * Descends from the TreeNode `node`, returning - * the node with value `value` if found or `null` - * otherwise - */ -function get (node, value) { - if (node.value === value) return node; - for (let child of node.children) { - let found = get(child, value); - if (found) return found; - } - return null; -} - -/* - * Constructs a tree of bookmark nodes - * returning the root (value: null); - */ - -function constructTree (items) { - let root = TreeNode(null); - items.forEach(treeify.bind(null, root)); - - function treeify (root, item) { - // If node already exists, skip - let node = root.get(item); - if (node) return node; - node = TreeNode(item); - - let parentNode = item.group ? treeify(root, item.group) : root; - parentNode.add(node); - - return node; - } - - return root; -} -exports.constructTree = constructTree; - -/* - * Shortcut for converting an id, or an object with an id, into - * an object with corresponding bookmark data - */ -function fetchItem (item) { - return send('sdk-places-bookmarks-get', { id: item.id || item }); -} -exports.fetchItem = fetchItem; - -/* - * Takes an ID or an object with ID and checks it against - * the root bookmark folders - */ -function isRootGroup (id) { - id = id && id.id; - return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder, - bmsrv.unfiledBookmarksFolder - ].indexOf(id); -} -exports.isRootGroup = isRootGroup; - -/* - * Merges appropriate options into query based off of url - * 4 scenarios: - * - * 'moz.com' // domain: moz.com, domainIsHost: true - * --> 'http://moz.com', 'http://moz.com/thunderbird' - * '*.moz.com' // domain: moz.com, domainIsHost: false - * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test' - * 'http://moz.com' // uri: http://moz.com/ - * --> 'http://moz.com/' - * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true - * --> 'http://moz.com/', 'http://moz.com/thunderbird' - */ - -function urlQueryParser (query, url) { - if (!url) return; - if (/^https?:\/\//.test(url)) { - query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/'; - if (/\*$/.test(url)) { - // Wildcard searches on URIs are not supported, so try to extract a - // domain and filter the data later. - url = url.replace(/\*$/, ''); - try { - query.domain = new URL(url).hostname; - query.domainIsHost = true; - // Unfortunately here we cannot use an expando to store the wildcard, - // cause the query is a wrapped native XPCOM object, so we reuse uri. - // We clearly don't want to query for both uri and domain, thus we'll - // have to handle this in host-query.js::execute() - query.uri = url; - } catch (ex) { - // Cannot extract an host cause it's not a valid uri, the query will - // just return nothing. - } - } - } else { - if (/^\*/.test(url)) { - query.domain = url.replace(/^\*\./, ''); - query.domainIsHost = false; - } else { - query.domain = url; - query.domainIsHost = true; - } - } -} -exports.urlQueryParser = urlQueryParser; - -/* - * Takes an EventEmitter and returns a promise that - * aggregates results and handles a bulk resolve and reject - */ - -function promisedEmitter (emitter) { - let { promise, resolve, reject } = defer(); - let errors = []; - emitter.on('error', error => errors.push(error)); - emitter.on('end', (items) => { - if (errors.length) reject(errors[0]); - else resolve(items); - }); - return promise; -} -exports.promisedEmitter = promisedEmitter; - - -// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions -function createQuery (type, query) { - query = query || {}; - let qObj = { - searchTerms: query.query - }; - - urlQueryParser(qObj, query.url); - - // 0 === history - if (type === 0) { - // PRTime used by query is in microseconds, not milliseconds - qObj.beginTime = (query.from || 0) * 1000; - qObj.endTime = (query.to || new Date()) * 1000; - - // Set reference time to Epoch - qObj.beginTimeReference = 0; - qObj.endTimeReference = 0; - } - // 1 === bookmarks - else if (type === 1) { - qObj.tags = query.tags; - qObj.folder = query.group && query.group.id; - } - // 2 === unified (not implemented on platform) - else if (type === 2) { - - } - - return qObj; -} -exports.createQuery = createQuery; - -// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions - -const SORT_MAP = { - title: 1, - date: 3, // sort by visit date - url: 5, - visitCount: 7, - // keywords currently unsupported - // keyword: 9, - dateAdded: 11, // bookmarks only - lastModified: 13 // bookmarks only -}; - -function createQueryOptions (type, options) { - options = options || {}; - let oObj = {}; - oObj.sortingMode = SORT_MAP[options.sort] || 0; - if (options.descending && options.sort) - oObj.sortingMode++; - - // Resolve to default sort if ineligible based on query type - if (type === 0 && // history - (options.sort === 'dateAdded' || options.sort === 'lastModified')) - oObj.sortingMode = 0; - - oObj.maxResults = typeof options.count === 'number' ? options.count : 0; - - oObj.queryType = type; - - return oObj; -} -exports.createQueryOptions = createQueryOptions; - - -function mapBookmarkItemType (type) { - if (typeof type === 'number') { - if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark'; - if (bmsrv.TYPE_FOLDER === type) return 'group'; - if (bmsrv.TYPE_SEPARATOR === type) return 'separator'; - } else { - if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK; - if ('group' === type) return bmsrv.TYPE_FOLDER; - if ('separator' === type) return bmsrv.TYPE_SEPARATOR; - } -} -exports.mapBookmarkItemType = mapBookmarkItemType; diff --git a/addon-sdk/source/lib/sdk/platform/xpcom.js b/addon-sdk/source/lib/sdk/platform/xpcom.js deleted file mode 100644 index 383baf67a..000000000 --- a/addon-sdk/source/lib/sdk/platform/xpcom.js +++ /dev/null @@ -1,241 +0,0 @@ -/* 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 { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome'); -const { registerFactory, unregisterFactory, isCIDRegistered } = - Cm.QueryInterface(Ci.nsIComponentRegistrar); - -const { merge } = require('../util/object'); -const { Class, extend, mix } = require('../core/heritage'); -const { uuid } = require('../util/uuid'); - -// This is a base prototype, that provides bare bones of XPCOM. JS based -// components can be easily implement by extending it. -const Unknown = new function() { - function hasInterface(component, iid) { - return component && component.interfaces && - ( component.interfaces.some(id => iid.equals(Ci[id])) || - component.implements.some($ => hasInterface($, iid)) || - hasInterface(Object.getPrototypeOf(component), iid)); - } - - return Class({ - /** - * The `QueryInterface` method provides runtime type discovery used by XPCOM. - * This method return queried instance of `this` if given `iid` is listed in - * the `interfaces` property or in equivalent properties of objects in it's - * prototype chain. In addition it will look up in the prototypes under - * `implements` array property, this ways compositions made via `Class` - * utility will carry interfaces implemented by composition components. - */ - QueryInterface: function QueryInterface(iid) { - // For some reason there are cases when `iid` is `null`. In such cases we - // just return `this`. Otherwise we verify that component implements given - // `iid` interface. This will be no longer necessary once Bug 748003 is - // fixed. - if (iid && !hasInterface(this, iid)) - throw Cr.NS_ERROR_NO_INTERFACE; - - return this; - }, - /** - * Array of `XPCOM` interfaces (as strings) implemented by this component. - * All components implement `nsISupports` by default which is default value - * here. Provide array of interfaces implemented by an object when - * extending, to append them to this list (Please note that there is no - * need to repeat interfaces implemented by super as they will be added - * automatically). - */ - interfaces: Object.freeze([ 'nsISupports' ]) - }); -} -exports.Unknown = Unknown; - -// Base exemplar for creating instances implementing `nsIFactory` interface, -// that maybe registered into runtime via `register` function. Instances of -// this factory create instances of enclosed component on `createInstance`. -const Factory = Class({ - extends: Unknown, - interfaces: [ 'nsIFactory' ], - /** - * All the descendants will get auto generated `id` (also known as `classID` - * in XPCOM world) unless one is manually provided. - */ - get id() { throw Error('Factory must implement `id` property') }, - /** - * XPCOM `contractID` may optionally be provided to associate this factory - * with it. `contract` is a unique string that has a following format: - * '@vendor.com/unique/id;1'. - */ - contract: null, - /** - * Class description that is being registered. This value is intended as a - * human-readable description for the given class and does not needs to be - * globally unique. - */ - description: 'Jetpack generated factory', - /** - * This method is required by `nsIFactory` interfaces, but as in most - * implementations it does nothing interesting. - */ - lockFactory: function lockFactory(lock) { - return undefined; - }, - /** - * If property is `true` XPCOM service / factory will be registered - * automatically on creation. - */ - register: true, - /** - * If property is `true` XPCOM factory will be unregistered prior to add-on - * unload. - */ - unregister: true, - /** - * Method is called on `Service.new(options)` passing given `options` to - * it. Options is expected to have `component` property holding XPCOM - * component implementation typically decedent of `Unknown` or any custom - * implementation with a `new` method and optional `register`, `unregister` - * flags. Unless `register` is `false` Service / Factory will be - * automatically registered. Unless `unregister` is `false` component will - * be automatically unregistered on add-on unload. - */ - initialize: function initialize(options) { - merge(this, { - id: 'id' in options ? options.id : uuid(), - register: 'register' in options ? options.register : this.register, - unregister: 'unregister' in options ? options.unregister : this.unregister, - contract: 'contract' in options ? options.contract : null, - Component: options.Component - }); - - // If service / factory has auto registration enabled then register. - if (this.register) - register(this); - }, - /** - * Creates an instance of the class associated with this factory. - */ - createInstance: function createInstance(outer, iid) { - try { - if (outer) - throw Cr.NS_ERROR_NO_AGGREGATION; - return this.create().QueryInterface(iid); - } - catch (error) { - throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE; - } - }, - create: function create() { - return this.Component(); - } -}); -exports.Factory = Factory; - -// Exemplar for creating services that implement `nsIFactory` interface, that -// can be registered into runtime via call to `register`. This services return -// enclosed `component` on `getService`. -const Service = Class({ - extends: Factory, - initialize: function initialize(options) { - this.component = options.Component(); - Factory.prototype.initialize.call(this, options); - }, - description: 'Jetpack generated service', - /** - * Creates an instance of the class associated with this factory. - */ - create: function create() { - return this.component; - } -}); -exports.Service = Service; - -function isRegistered({ id }) { - return isCIDRegistered(id); -} -exports.isRegistered = isRegistered; - -/** - * Registers given `component` object to be used to instantiate a particular - * class identified by `component.id`, and creates an association of class - * name and `component.contract` with the class. - */ -function register(factory) { - if (!(factory instanceof Factory)) { - throw new Error("xpcom.register() expect a Factory instance.\n" + - "Please refactor your code to new xpcom module if you" + - " are repacking an addon from SDK <= 1.5:\n" + - "https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/platform_xpcom"); - } - - registerFactory(factory.id, factory.description, factory.contract, factory); - - if (factory.unregister) - require('../system/unload').when(unregister.bind(null, factory)); -} -exports.register = register; - -/** - * Unregister a factory associated with a particular class identified by - * `factory.classID`. - */ -function unregister(factory) { - if (isRegistered(factory)) - unregisterFactory(factory.id, factory); -} -exports.unregister = unregister; - -function autoRegister(path) { - // TODO: This assumes that the url points to a directory - // that contains subdirectories corresponding to OS/ABI and then - // further subdirectories corresponding to Gecko platform version. - // we should probably either behave intelligently here or allow - // the caller to pass-in more options if e.g. there aren't - // Gecko-specific binaries for a component (which will be the case - // if only frozen interfaces are used). - - var runtime = require("../system/runtime"); - var osDirName = runtime.OS + "_" + runtime.XPCOMABI; - var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5); - - var file = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - file.initWithPath(path); - file.append(osDirName); - file.append(platformVersion); - - if (!(file.exists() && file.isDirectory())) - throw new Error("component not available for OS/ABI " + - osDirName + " and platform " + platformVersion); - - Cm.QueryInterface(Ci.nsIComponentRegistrar); - Cm.autoRegister(file); -} -exports.autoRegister = autoRegister; - -/** - * Returns registered factory that has a given `id` or `null` if not found. - */ -function factoryByID(id) { - return classesByID[id] || null; -} -exports.factoryByID = factoryByID; - -/** - * Returns factory registered with a given `contract` or `null` if not found. - * In contrast to `Cc[contract]` that does ignores new factory registration - * with a given `contract` this will return a factory currently associated - * with a `contract`. - */ -function factoryByContract(contract) { - return factoryByID(Cm.contractIDToCID(contract)); -} -exports.factoryByContract = factoryByContract; diff --git a/addon-sdk/source/lib/sdk/preferences/event-target.js b/addon-sdk/source/lib/sdk/preferences/event-target.js deleted file mode 100644 index b64ba303c..000000000 --- a/addon-sdk/source/lib/sdk/preferences/event-target.js +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 { Cc, Ci } = require('chrome'); -const { Class } = require('../core/heritage'); -const { EventTarget } = require('../event/target'); -const { Branch } = require('./service'); -const { emit, off } = require('../event/core'); -const { when: unload } = require('../system/unload'); - -const prefTargetNS = require('../core/namespace').ns(); - -const PrefsTarget = Class({ - extends: EventTarget, - initialize: function(options) { - options = options || {}; - EventTarget.prototype.initialize.call(this, options); - - let branchName = options.branchName || ''; - let branch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - getBranch(branchName). - QueryInterface(Ci.nsIPrefBranch2); - prefTargetNS(this).branch = branch; - - // provides easy access to preference values - this.prefs = Branch(branchName); - - // start listening to preference changes - let observer = prefTargetNS(this).observer = onChange.bind(this); - branch.addObserver('', observer, false); - - // Make sure to destroy this on unload - unload(destroy.bind(this)); - } -}); -exports.PrefsTarget = PrefsTarget; - -/* HELPERS */ - -function onChange(subject, topic, name) { - if (topic === 'nsPref:changed') { - emit(this, name, name); - emit(this, '', name); - } -} - -function destroy() { - off(this); - - // stop listening to preference changes - let branch = prefTargetNS(this).branch; - branch.removeObserver('', prefTargetNS(this).observer, false); - prefTargetNS(this).observer = null; -} diff --git a/addon-sdk/source/lib/sdk/preferences/native-options.js b/addon-sdk/source/lib/sdk/preferences/native-options.js deleted file mode 100644 index 840997df9..000000000 --- a/addon-sdk/source/lib/sdk/preferences/native-options.js +++ /dev/null @@ -1,193 +0,0 @@ -/* 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 { Cc, Ci, Cu } = require('chrome'); -const { on } = require('../system/events'); -const { id, preferencesBranch } = require('../self'); -const { localizeInlineOptions } = require('../l10n/prefs'); -const { Services } = require("resource://gre/modules/Services.jsm"); -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm"); -const { defer } = require("sdk/core/promise"); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";; -const DEFAULT_OPTIONS_URL = 'data:text/xml,<placeholder/>'; - -const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color', - 'file', 'directory', 'control', 'menulist', 'radio']; - -const isFennec = require("sdk/system/xul-app").is("Fennec"); - -function enable({ preferences, id }) { - let enabled = defer(); - - validate(preferences); - - setDefaults(preferences, preferencesBranch); - - // allow the use of custom options.xul - AddonManager.getAddonByID(id, (addon) => { - on('addon-options-displayed', onAddonOptionsDisplayed, true); - enabled.resolve({ id: id }); - }); - - function onAddonOptionsDisplayed({ subject: doc, data }) { - if (data === id) { - let parent; - - if (isFennec) { - parent = doc.querySelector('.options-box'); - - // NOTE: This disable the CSS rule that makes the options invisible - let item = doc.querySelector('#addons-details .addon-item'); - item.removeAttribute("optionsURL"); - } else { - parent = doc.getElementById('detail-downloads').parentNode; - } - - if (parent) { - injectOptions({ - preferences: preferences, - preferencesBranch: preferencesBranch, - document: doc, - parent: parent, - id: id - }); - localizeInlineOptions(doc); - } else { - throw Error("Preferences parent node not found in Addon Details. The configured custom preferences will not be visible."); - } - } - } - - return enabled.promise; -} -exports.enable = enable; - -// centralized sanity checks -function validate(preferences) { - for (let { name, title, type, label, options } of preferences) { - // make sure the title is set and non-empty - if (!title) - throw Error("The '" + name + "' pref requires a title"); - - // make sure that pref type is a valid inline option type - if (!~VALID_PREF_TYPES.indexOf(type)) - throw Error("The '" + name + "' pref must be of valid type"); - - // if it's a control, make sure it has a label - if (type === 'control' && !label) - throw Error("The '" + name + "' control requires a label"); - - // if it's a menulist or radio, make sure it has options - if (type === 'menulist' || type === 'radio') { - if (!options) - throw Error("The '" + name + "' pref requires options"); - - // make sure each option has a value and a label - for (let item of options) { - if (!('value' in item) || !('label' in item)) - throw Error("Each option requires both a value and a label"); - } - } - - // TODO: check that pref type matches default value type - } -} -exports.validate = validate; - -// initializes default preferences, emulates defaults/prefs.js -function setDefaults(preferences, preferencesBranch) { - const branch = Cc['@mozilla.org/preferences-service;1']. - getService(Ci.nsIPrefService). - getDefaultBranch('extensions.' + preferencesBranch + '.'); - for (let { name, value } of preferences) { - switch (typeof value) { - case 'boolean': - branch.setBoolPref(name, value); - break; - case 'number': - // must be integer, ignore otherwise - if (value % 1 === 0) { - branch.setIntPref(name, value); - } - break; - case 'string': - let str = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - str.data = value; - branch.setComplexValue(name, Ci.nsISupportsString, str); - break; - } - } -} -exports.setDefaults = setDefaults; - -// dynamically injects inline options into about:addons page at runtime -// NOTE: on Firefox Desktop the about:addons page is a xul page document, -// on Firefox for Android the about:addons page is an xhtml page, to support both -// the XUL xml namespace have to be enforced. -function injectOptions({ preferences, preferencesBranch, document, parent, id }) { - preferences.forEach(({name, type, hidden, title, description, label, options, on, off}) => { - if (hidden) { - return; - } - - let setting = document.createElementNS(XUL_NS, 'setting'); - setting.setAttribute('pref-name', name); - setting.setAttribute('data-jetpack-id', id); - setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name); - setting.setAttribute('type', type); - setting.setAttribute('title', title); - if (description) - setting.setAttribute('desc', description); - - if (type === 'file' || type === 'directory') { - setting.setAttribute('fullpath', 'true'); - } - else if (type === 'control') { - let button = document.createElementNS(XUL_NS, 'button'); - button.setAttribute('pref-name', name); - button.setAttribute('data-jetpack-id', id); - button.setAttribute('label', label); - button.addEventListener('command', function() { - Services.obs.notifyObservers(null, `${id}-cmdPressed`, name); - }, true); - setting.appendChild(button); - } - else if (type === 'boolint') { - setting.setAttribute('on', on); - setting.setAttribute('off', off); - } - else if (type === 'menulist') { - let menulist = document.createElementNS(XUL_NS, 'menulist'); - let menupopup = document.createElementNS(XUL_NS, 'menupopup'); - for (let { value, label } of options) { - let menuitem = document.createElementNS(XUL_NS, 'menuitem'); - menuitem.setAttribute('value', value); - menuitem.setAttribute('label', label); - menupopup.appendChild(menuitem); - } - menulist.appendChild(menupopup); - setting.appendChild(menulist); - } - else if (type === 'radio') { - let radiogroup = document.createElementNS(XUL_NS, 'radiogroup'); - for (let { value, label } of options) { - let radio = document.createElementNS(XUL_NS, 'radio'); - radio.setAttribute('value', value); - radio.setAttribute('label', label); - radiogroup.appendChild(radio); - } - setting.appendChild(radiogroup); - } - - parent.appendChild(setting); - }); -} -exports.injectOptions = injectOptions; diff --git a/addon-sdk/source/lib/sdk/preferences/service.js b/addon-sdk/source/lib/sdk/preferences/service.js deleted file mode 100644 index 231cd8e14..000000000 --- a/addon-sdk/source/lib/sdk/preferences/service.js +++ /dev/null @@ -1,137 +0,0 @@ -/* 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" -}; - -// The minimum and maximum integers that can be set as preferences. -// The range of valid values is narrower than the range of valid JS values -// because the native preferences code treats integers as NSPR PRInt32s, -// which are 32-bit signed integers on all platforms. -const MAX_INT = 0x7FFFFFFF; -const MIN_INT = -0x80000000; - -const {Cc,Ci,Cr} = require("chrome"); - -const prefService = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService); -const prefSvc = prefService.getBranch(null); -const defaultBranch = prefService.getDefaultBranch(null); - -const { Preferences } = require("resource://gre/modules/Preferences.jsm"); -const prefs = new Preferences({}); - -const branchKeys = branchName => - keys(branchName).map($ => $.replace(branchName, "")); - -const Branch = function(branchName) { - return new Proxy(Branch.prototype, { - getOwnPropertyDescriptor(target, name, receiver) { - return { - configurable: true, - enumerable: true, - writable: false, - value: this.get(target, name, receiver) - }; - }, - ownKeys(target) { - return branchKeys(branchName); - }, - get(target, name, receiver) { - return get(`${branchName}${name}`); - }, - set(target, name, value, receiver) { - set(`${branchName}${name}`, value); - return true; - }, - has(target, name) { - return this.hasOwn(target, name); - }, - hasOwn(target, name) { - return has(`${branchName}${name}`); - }, - deleteProperty(target, name) { - reset(`${branchName}${name}`); - return true; - } - }); -} - - -function get(name, defaultValue) { - return prefs.get(name, defaultValue); -} -exports.get = get; - - -function set(name, value) { - var prefType; - if (typeof value != "undefined" && value != null) - prefType = value.constructor.name; - - switch (prefType) { - case "Number": - if (value % 1 != 0) - throw new Error("cannot store non-integer number: " + value); - } - - prefs.set(name, value); -} -exports.set = set; - -const has = prefs.has.bind(prefs) -exports.has = has; - -function keys(root) { - return prefSvc.getChildList(root); -} -exports.keys = keys; - -const isSet = prefs.isSet.bind(prefs); -exports.isSet = isSet; - -function reset(name) { - try { - prefSvc.clearUserPref(name); - } - catch (e) { - // The pref service throws NS_ERROR_UNEXPECTED when the caller tries - // to reset a pref that doesn't exist or is already set to its default - // value. This interface fails silently in those cases, so callers - // can unconditionally reset a pref without having to check if it needs - // resetting first or trap exceptions after the fact. It passes through - // other exceptions, however, so callers know about them, since we don't - // know what other exceptions might be thrown and what they might mean. - if (e.result != Cr.NS_ERROR_UNEXPECTED) { - throw e; - } - } -} -exports.reset = reset; - -function getLocalized(name, defaultValue) { - let value = null; - try { - value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data; - } - finally { - return value || defaultValue; - } -} -exports.getLocalized = getLocalized; - -function setLocalized(name, value) { - // We can't use `prefs.set` here as we have to use `getDefaultBranch` - // (instead of `getBranch`) in order to have `mIsDefault` set to true, here: - // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233 - // Otherwise, we do not enter into this expected condition: - // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244 - defaultBranch.setCharPref(name, value); -} -exports.setLocalized = setLocalized; - -exports.Branch = Branch; - diff --git a/addon-sdk/source/lib/sdk/preferences/utils.js b/addon-sdk/source/lib/sdk/preferences/utils.js deleted file mode 100644 index 1d5769c37..000000000 --- a/addon-sdk/source/lib/sdk/preferences/utils.js +++ /dev/null @@ -1,42 +0,0 @@ -/* 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 { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils"); -const { on, off } = require("sdk/system/events"); -const { getMostRecentBrowserWindow } = require('../window/utils'); - -// Opens about:addons in a new tab, then displays the inline -// preferences of the provided add-on -const open = ({ id }) => new Promise((resolve, reject) => { - // opening the about:addons page in a new tab - let tab = openTab(getMostRecentBrowserWindow(), "about:addons"); - let browser = getBrowserForTab(tab); - - // waiting for the about:addons page to load - browser.addEventListener("load", function onPageLoad() { - browser.removeEventListener("load", onPageLoad, true); - let window = browser.contentWindow; - - // wait for the add-on's "addon-options-displayed" - on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) { - if (data === id) { - off("addon-options-displayed", onPrefDisplayed); - resolve({ - id: id, - tabId: getTabId(tab), - "document": doc - }); - } - }, true); - - // display the add-on inline preferences page - window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true); - }, true); -}); -exports.open = open; diff --git a/addon-sdk/source/lib/sdk/private-browsing.js b/addon-sdk/source/lib/sdk/private-browsing.js deleted file mode 100644 index 29ca16185..000000000 --- a/addon-sdk/source/lib/sdk/private-browsing.js +++ /dev/null @@ -1,12 +0,0 @@ -/* 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": "stable" -}; - -const { isPrivate } = require('./private-browsing/utils'); - -exports.isPrivate = isPrivate; diff --git a/addon-sdk/source/lib/sdk/private-browsing/utils.js b/addon-sdk/source/lib/sdk/private-browsing/utils.js deleted file mode 100644 index 8b012f0ce..000000000 --- a/addon-sdk/source/lib/sdk/private-browsing/utils.js +++ /dev/null @@ -1,54 +0,0 @@ -/* 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 { Cc, Ci, Cu } = require('chrome'); -const { is } = require('../system/xul-app'); -const { isWindowPrivate } = require('../window/utils'); -const { isPrivateBrowsingSupported } = require('../self'); -const { dispatcher } = require("../util/dispatcher"); - -var PrivateBrowsingUtils; - -// Private browsing is only supported in Fx -try { - PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils; -} -catch (e) {} - -exports.isGlobalPBSupported = false; - -// checks that per-window private browsing is implemented -var isWindowPBSupported = exports.isWindowPBSupported = - !!PrivateBrowsingUtils && is('Firefox'); - -// checks that per-tab private browsing is implemented -var isTabPBSupported = exports.isTabPBSupported = - !!PrivateBrowsingUtils && is('Fennec'); - -function isPermanentPrivateBrowsing() { - return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing); -} -exports.isPermanentPrivateBrowsing = isPermanentPrivateBrowsing; - -function ignoreWindow(window) { - return !isPrivateBrowsingSupported && isWindowPrivate(window); -} -exports.ignoreWindow = ignoreWindow; - -var getMode = function getMode(chromeWin) { - return (chromeWin !== undefined && isWindowPrivate(chromeWin)); -}; -exports.getMode = getMode; - -const isPrivate = dispatcher("isPrivate"); -isPrivate.when(isPermanentPrivateBrowsing, _ => true); -isPrivate.when(x => x instanceof Ci.nsIDOMWindow, isWindowPrivate); -isPrivate.when(x => Ci.nsIPrivateBrowsingChannel && x instanceof Ci.nsIPrivateBrowsingChannel, x => x.isChannelPrivate); -isPrivate.define(() => false); -exports.isPrivate = isPrivate; diff --git a/addon-sdk/source/lib/sdk/querystring.js b/addon-sdk/source/lib/sdk/querystring.js deleted file mode 100644 index 9982a00ab..000000000 --- a/addon-sdk/source/lib/sdk/querystring.js +++ /dev/null @@ -1,121 +0,0 @@ -/* 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" -}; - -var unescape = decodeURIComponent; -exports.unescape = unescape; - -// encodes a string safely for application/x-www-form-urlencoded -// adheres to RFC 3986 -// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent -function escape(query) { - return encodeURIComponent(query). - replace(/%20/g, '+'). - replace(/!/g, '%21'). - replace(/'/g, '%27'). - replace(/\(/g, '%28'). - replace(/\)/g, '%29'). - replace(/\*/g, '%2A'); -} -exports.escape = escape; - -// Converts an object of unordered key-vals to a string that can be passed -// as part of a request -function stringify(options, separator, assigner) { - separator = separator || '&'; - assigner = assigner || '='; - // Explicitly return null if we have null, and empty string, or empty object. - if (!options) - return ''; - - // If content is already a string, just return it as is. - if (typeof(options) == 'string') - return options; - - // At this point we have a k:v object. Iterate over it and encode each value. - // Arrays and nested objects will get encoded as needed. For example... - // - // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' } - // - // will be encoded as - // - // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz - // - // Keys (including '[' and ']') and values will be encoded with - // `escape` before returning. - // - // Execution was inspired by jQuery, but some details have changed and numeric - // array keys are included (whereas they are not in jQuery). - - let encodedContent = []; - function add(key, val) { - encodedContent.push(escape(key) + assigner + escape(val)); - } - - function make(key, value) { - if (value && typeof(value) === 'object') - Object.keys(value).forEach(function(name) { - make(key + '[' + name + ']', value[name]); - }); - else - add(key, value); - } - - Object.keys(options).forEach(function(name) { make(name, options[name]); }); - return encodedContent.join(separator); - - //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had - // trouble getting that working. It would also be nice to stay - // backwards-compat as long as possible. Keeping this in for now... - // let formData = Cc['@mozilla.org/files/formdata;1']. - // createInstance(Ci.nsIDOMFormData); - // for ([k, v] in Iterator(content)) { - // formData.append(k, v); - // } - // return formData; -} -exports.stringify = stringify; - -// Exporting aliases that nodejs implements just for the sake of -// interoperability. -exports.encode = stringify; -exports.serialize = stringify; - -// Note: That `stringify` and `parse` aren't bijective as we use `stringify` -// as it was implement in request module, but implement `parse` to match nodejs -// behavior. -// TODO: Make `stringify` implement API as in nodejs and figure out backwards -// compatibility. -function parse(query, separator, assigner) { - separator = separator || '&'; - assigner = assigner || '='; - let result = {}; - - if (typeof query !== 'string' || query.length === 0) - return result; - - query.split(separator).forEach(function(chunk) { - let pair = chunk.split(assigner); - let key = unescape(pair[0]); - let value = unescape(pair.slice(1).join(assigner)); - - if (!(key in result)) - result[key] = value; - else if (Array.isArray(result[key])) - result[key].push(value); - else - result[key] = [result[key], value]; - }); - - return result; -}; -exports.parse = parse; -// Exporting aliases that nodejs implements just for the sake of -// interoperability. -exports.decode = parse; diff --git a/addon-sdk/source/lib/sdk/remote/child.js b/addon-sdk/source/lib/sdk/remote/child.js deleted file mode 100644 index 4ccfa661a..000000000 --- a/addon-sdk/source/lib/sdk/remote/child.js +++ /dev/null @@ -1,284 +0,0 @@ -/* 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 { isChildLoader } = require('./core'); -if (!isChildLoader) - throw new Error("Cannot load sdk/remote/child in a main process loader."); - -const { Ci, Cc, Cu } = require('chrome'); -const runtime = require('../system/runtime'); -const { Class } = require('../core/heritage'); -const { Namespace } = require('../core/namespace'); -const { omit } = require('../util/object'); -const { when } = require('../system/unload'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const { Disposable } = require('../core/disposable'); -const { EventParent } = require('./utils'); -const { addListItem, removeListItem } = require('../util/list'); - -const loaderID = require('@loader/options').loaderID; - -const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; - -const mm = Cc['@mozilla.org/childprocessmessagemanager;1']. - getService(Ci.nsISyncMessageSender); - -const ns = Namespace(); - -const process = { - port: new EventTarget(), - get id() { - return runtime.processID; - }, - get isRemote() { - return runtime.processType != MAIN_PROCESS; - } -}; -exports.process = process; - -function definePort(obj, name) { - obj.port.emit = (event, ...args) => { - let manager = ns(obj).messageManager; - if (!manager) - return; - - manager.sendAsyncMessage(name, { loaderID, event, args }); - }; -} - -function messageReceived({ data, objects }) { - // Ignore messages from other loaders - if (data.loaderID != loaderID) - return; - - let keys = Object.keys(objects); - if (keys.length) { - // If any objects are CPOWs then ignore this message. We don't want child - // processes interracting with CPOWs - if (!keys.every(name => !Cu.isCrossProcessWrapper(objects[name]))) - return; - - data.args.push(objects); - } - - emit(this.port, data.event, this, ...data.args); -} - -ns(process).messageManager = mm; -definePort(process, 'sdk/remote/process/message'); -let processMessageReceived = messageReceived.bind(process); -mm.addMessageListener('sdk/remote/process/message', processMessageReceived); - -when(() => { - mm.removeMessageListener('sdk/remote/process/message', processMessageReceived); - frames = null; -}); - -process.port.on('sdk/remote/require', (process, uri) => { - require(uri); -}); - -function listenerEquals(a, b) { - for (let prop of ["type", "callback", "isCapturing"]) { - if (a[prop] != b[prop]) - return false; - } - return true; -} - -function listenerFor(type, callback, isCapturing = false) { - return { - type, - callback, - isCapturing, - registeredCallback: undefined, - get args() { - return [ - this.type, - this.registeredCallback ? this.registeredCallback : this.callback, - this.isCapturing - ]; - } - }; -} - -function removeListenerFromArray(array, listener) { - let index = array.findIndex(l => listenerEquals(l, listener)); - if (index < 0) - return; - array.splice(index, 1); -} - -function getListenerFromArray(array, listener) { - return array.find(l => listenerEquals(l, listener)); -} - -function arrayContainsListener(array, listener) { - return !!getListenerFromArray(array, listener); -} - -function makeFrameEventListener(frame, callback) { - return callback.bind(frame); -} - -var FRAME_ID = 0; -var tabMap = new Map(); - -const Frame = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(contentFrame) { - // This ID should be unique for this loader across all processes - let priv = ns(this); - - priv.id = runtime.processID + ":" + FRAME_ID++; - - priv.contentFrame = contentFrame; - priv.messageManager = contentFrame; - priv.domListeners = []; - - tabMap.set(contentFrame.docShell, this); - - priv.messageReceived = messageReceived.bind(this); - priv.messageManager.addMessageListener('sdk/remote/frame/message', priv.messageReceived); - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/frame/message'); - - priv.messageManager.sendAsyncMessage('sdk/remote/frame/attach', { - loaderID, - frameID: priv.id, - processID: runtime.processID - }); - - frames.attachItem(this); - }, - - dispose: function() { - let priv = ns(this); - - emit(this, 'detach', this); - - for (let listener of priv.domListeners) - priv.contentFrame.removeEventListener(...listener.args); - - priv.messageManager.removeMessageListener('sdk/remote/frame/message', priv.messageReceived); - tabMap.delete(priv.contentFrame.docShell); - priv.contentFrame = null; - }, - - get content() { - return ns(this).contentFrame.content; - }, - - get isTab() { - let docShell = ns(this).contentFrame.docShell; - if (process.isRemote) { - // We don't want to roundtrip to the main process to get this property. - // This hack relies on the host app having defined webBrowserChrome only - // in frames that are part of the tabs. Since only Firefox has remote - // processes right now and does this this works. - let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsITabChild); - return !!tabchild.webBrowserChrome; - } - else { - // This is running in the main process so we can break out to the browser - // And check we can find a tab for the browser element directly. - let browser = docShell.chromeEventHandler; - let tab = require('../tabs/utils').getTabForBrowser(browser); - return !!tab; - } - }, - - addEventListener: function(...args) { - let priv = ns(this); - - let listener = listenerFor(...args); - if (arrayContainsListener(priv.domListeners, listener)) - return; - - listener.registeredCallback = makeFrameEventListener(this, listener.callback); - - priv.domListeners.push(listener); - priv.contentFrame.addEventListener(...listener.args); - }, - - removeEventListener: function(...args) { - let priv = ns(this); - - let listener = getListenerFromArray(priv.domListeners, listenerFor(...args)); - if (!listener) - return; - - removeListenerFromArray(priv.domListeners, listener); - priv.contentFrame.removeEventListener(...listener.args); - } -}); - -const FrameList = Class({ - implements: [ EventParent, Disposable ], - extends: EventTarget, - setup: function() { - EventParent.prototype.initialize.call(this); - - this.port = new EventTarget(); - ns(this).domListeners = []; - - this.on('attach', frame => { - for (let listener of ns(this).domListeners) - frame.addEventListener(...listener.args); - }); - }, - - dispose: function() { - // The only case where we get destroyed is when the loader is unloaded in - // which case each frame will clean up its own event listeners. - ns(this).domListeners = null; - }, - - getFrameForWindow: function(window) { - let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - - return tabMap.get(docShell) || null; - }, - - addEventListener: function(...args) { - let listener = listenerFor(...args); - if (arrayContainsListener(ns(this).domListeners, listener)) - return; - - ns(this).domListeners.push(listener); - for (let frame of this) - frame.addEventListener(...listener.args); - }, - - removeEventListener: function(...args) { - let listener = listenerFor(...args); - if (!arrayContainsListener(ns(this).domListeners, listener)) - return; - - removeListenerFromArray(ns(this).domListeners, listener); - for (let frame of this) - frame.removeEventListener(...listener.args); - } -}); -var frames = exports.frames = new FrameList(); - -function registerContentFrame(contentFrame) { - let frame = new Frame(contentFrame); -} -exports.registerContentFrame = registerContentFrame; - -function unregisterContentFrame(contentFrame) { - let frame = tabMap.get(contentFrame.docShell); - if (!frame) - return; - - frame.destroy(); -} -exports.unregisterContentFrame = unregisterContentFrame; diff --git a/addon-sdk/source/lib/sdk/remote/core.js b/addon-sdk/source/lib/sdk/remote/core.js deleted file mode 100644 index 78bb673fd..000000000 --- a/addon-sdk/source/lib/sdk/remote/core.js +++ /dev/null @@ -1,8 +0,0 @@ -/* 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 options = require("@loader/options"); - -exports.isChildLoader = options.childLoader; diff --git a/addon-sdk/source/lib/sdk/remote/parent.js b/addon-sdk/source/lib/sdk/remote/parent.js deleted file mode 100644 index f110fe3f6..000000000 --- a/addon-sdk/source/lib/sdk/remote/parent.js +++ /dev/null @@ -1,338 +0,0 @@ -/* 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 { isChildLoader } = require('./core'); -if (isChildLoader) - throw new Error("Cannot load sdk/remote/parent in a child loader."); - -const { Cu, Ci, Cc } = require('chrome'); -const runtime = require('../system/runtime'); - -const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; - -if (runtime.processType != MAIN_PROCESS) { - throw new Error('Cannot use sdk/remote/parent in a child process.'); -} - -const { Class } = require('../core/heritage'); -const { Namespace } = require('../core/namespace'); -const { Disposable } = require('../core/disposable'); -const { omit } = require('../util/object'); -const { when } = require('../system/unload'); -const { EventTarget } = require('../event/target'); -const { emit } = require('../event/core'); -const system = require('../system/events'); -const { EventParent } = require('./utils'); -const options = require('@loader/options'); -const loaderModule = require('toolkit/loader'); -const { getTabForBrowser } = require('../tabs/utils'); - -const appInfo = Cc["@mozilla.org/xre/app-info;1"]. - getService(Ci.nsIXULRuntime); - -exports.useRemoteProcesses = appInfo.browserTabsRemoteAutostart; - -// Chose the right function for resolving relative a module id -var moduleResolve; -if (options.isNative) { - moduleResolve = (id, requirer) => loaderModule.nodeResolve(id, requirer, { rootURI: options.rootURI }); -} -else { - moduleResolve = loaderModule.resolve; -} -// Build the sorted path mapping structure that resolveURI requires -var pathMapping = Object.keys(options.paths) - .sort((a, b) => b.length - a.length) - .map(p => [p, options.paths[p]]); - -// Load the scripts in the child processes -var { getNewLoaderID } = require('../../framescript/FrameScriptManager.jsm'); -var PATH = options.paths['']; - -const childOptions = omit(options, ['modules', 'globals', 'resolve', 'load']); -childOptions.modules = {}; -// @l10n/data is just JSON data and can be safely sent across to the child loader -try { - childOptions.modules["@l10n/data"] = require("@l10n/data"); -} -catch (e) { - // There may be no l10n data -} -const loaderID = getNewLoaderID(); -childOptions.loaderID = loaderID; -childOptions.childLoader = true; - -const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']. - getService(Ci.nsIMessageBroadcaster); -const gmm = Cc['@mozilla.org/globalmessagemanager;1']. - getService(Ci.nsIMessageBroadcaster); - -const ns = Namespace(); - -var processMap = new Map(); - -function definePort(obj, name) { - obj.port.emitCPOW = (event, args, cpows = {}) => { - let manager = ns(obj).messageManager; - if (!manager) - return; - - let method = manager instanceof Ci.nsIMessageBroadcaster ? - "broadcastAsyncMessage" : "sendAsyncMessage"; - - manager[method](name, { loaderID, event, args }, cpows); - }; - - obj.port.emit = (event, ...args) => obj.port.emitCPOW(event, args); -} - -function messageReceived({ target, data }) { - // Ignore messages from other loaders - if (data.loaderID != loaderID) - return; - - emit(this.port, data.event, this, ...data.args); -} - -// Process represents a gecko process that can load webpages. Each process -// contains a number of Frames. This class is used to send and receive messages -// from a single process. -const Process = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(id, messageManager, isRemote) { - ns(this).id = id; - ns(this).isRemote = isRemote; - ns(this).messageManager = messageManager; - ns(this).messageReceived = messageReceived.bind(this); - this.destroy = this.destroy.bind(this); - ns(this).messageManager.addMessageListener('sdk/remote/process/message', ns(this).messageReceived); - ns(this).messageManager.addMessageListener('child-process-shutdown', this.destroy); - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/process/message'); - - // Load any remote modules - for (let module of remoteModules.values()) - this.port.emit('sdk/remote/require', module); - - processMap.set(ns(this).id, this); - processes.attachItem(this); - }, - - dispose: function() { - emit(this, 'detach', this); - processMap.delete(ns(this).id); - ns(this).messageManager.removeMessageListener('sdk/remote/process/message', ns(this).messageReceived); - ns(this).messageManager.removeMessageListener('child-process-shutdown', this.destroy); - ns(this).messageManager = null; - }, - - // Returns true if this process is a child process - get isRemote() { - return ns(this).isRemote; - } -}); - -// Processes gives an API for enumerating an sending and receiving messages from -// all processes as well as detecting when a new process starts. -const Processes = Class({ - implements: [ EventParent ], - extends: EventTarget, - initialize: function() { - EventParent.prototype.initialize.call(this); - ns(this).messageManager = ppmm; - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/process/message'); - }, - - getById: function(id) { - return processMap.get(id); - } -}); -var processes = exports.processes = new Processes(); - -var frameMap = new Map(); - -function setFrameProcess(frame, process) { - ns(frame).process = process; - frames.attachItem(frame); -} - -// Frames display webpages in a process. In the main process every Frame is -// linked with a <browser> or <iframe> element. -const Frame = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(id, node) { - ns(this).id = id; - ns(this).node = node; - - let frameLoader = node.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; - ns(this).messageManager = frameLoader.messageManager; - - ns(this).messageReceived = messageReceived.bind(this); - ns(this).messageManager.addMessageListener('sdk/remote/frame/message', ns(this).messageReceived); - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/frame/message'); - - frameMap.set(ns(this).messageManager, this); - }, - - dispose: function() { - emit(this, 'detach', this); - ns(this).messageManager.removeMessageListener('sdk/remote/frame/message', ns(this).messageReceived); - - frameMap.delete(ns(this).messageManager); - ns(this).messageManager = null; - }, - - // Returns the browser or iframe element this frame displays in - get frameElement() { - return ns(this).node; - }, - - // Returns the process that this frame loads in - get process() { - return ns(this).process; - }, - - // Returns true if this frame is a tab in a main browser window - get isTab() { - let tab = getTabForBrowser(ns(this).node); - return !!tab; - } -}); - -function managerDisconnected({ subject: manager }) { - let frame = frameMap.get(manager); - if (frame) - frame.destroy(); -} -system.on('message-manager-disconnect', managerDisconnected); - -// Provides an API for enumerating and sending and receiving messages from all -// Frames -const FrameList = Class({ - implements: [ EventParent ], - extends: EventTarget, - initialize: function() { - EventParent.prototype.initialize.call(this); - ns(this).messageManager = gmm; - - this.port = new EventTarget(); - definePort(this, 'sdk/remote/frame/message'); - }, - - // Returns the frame for a browser element - getFrameForBrowser: function(browser) { - for (let frame of this) { - if (frame.frameElement == browser) - return frame; - } - return null; - }, -}); -var frames = exports.frames = new FrameList(); - -// Create the module loader in any existing processes -ppmm.broadcastAsyncMessage('sdk/remote/process/load', { - modulePath: PATH, - loaderID, - options: childOptions, - reason: "broadcast" -}); - -// A loader has started in a remote process -function processLoaderStarted({ target, data }) { - if (data.loaderID != loaderID) - return; - - if (processMap.has(data.processID)) { - console.error("Saw the same process load the same loader twice. This is a bug in the SDK."); - return; - } - - let process = new Process(data.processID, target, data.isRemote); - - if (pendingFrames.has(data.processID)) { - for (let frame of pendingFrames.get(data.processID)) - setFrameProcess(frame, process); - pendingFrames.delete(data.processID); - } -} - -// A new process has started -function processStarted({ target, data: { modulePath } }) { - if (modulePath != PATH) - return; - - // Have it load a loader if it hasn't already - target.sendAsyncMessage('sdk/remote/process/load', { - modulePath, - loaderID, - options: childOptions, - reason: "response" - }); -} - -var pendingFrames = new Map(); - -// A new frame has been created in the remote process -function frameAttached({ target, data }) { - if (data.loaderID != loaderID) - return; - - let frame = new Frame(data.frameID, target); - - let process = processMap.get(data.processID); - if (process) { - setFrameProcess(frame, process); - return; - } - - // In some cases frame messages can arrive earlier than process messages - // causing us to see a new frame appear before its process. In this case - // cache the frame data until we see the process. See bug 1131375. - if (!pendingFrames.has(data.processID)) - pendingFrames.set(data.processID, [frame]); - else - pendingFrames.get(data.processID).push(frame); -} - -// Wait for new processes and frames -ppmm.addMessageListener('sdk/remote/process/attach', processLoaderStarted); -ppmm.addMessageListener('sdk/remote/process/start', processStarted); -gmm.addMessageListener('sdk/remote/frame/attach', frameAttached); - -when(reason => { - ppmm.removeMessageListener('sdk/remote/process/attach', processLoaderStarted); - ppmm.removeMessageListener('sdk/remote/process/start', processStarted); - gmm.removeMessageListener('sdk/remote/frame/attach', frameAttached); - - ppmm.broadcastAsyncMessage('sdk/remote/process/unload', { loaderID, reason }); -}); - -var remoteModules = new Set(); - -// Ensures a module is loaded in every child process. It is safe to send -// messages to this module immediately after calling this. -// Pass a module to resolve the id relatively. -function remoteRequire(id, module = null) { - // Resolve relative to calling module if passed - if (module) - id = moduleResolve(id, module.id); - let uri = loaderModule.resolveURI(id, pathMapping); - - // Don't reload the same module - if (remoteModules.has(uri)) - return; - - remoteModules.add(uri); - processes.port.emit('sdk/remote/require', uri); -} -exports.remoteRequire = remoteRequire; diff --git a/addon-sdk/source/lib/sdk/remote/utils.js b/addon-sdk/source/lib/sdk/remote/utils.js deleted file mode 100644 index 5a5e39198..000000000 --- a/addon-sdk/source/lib/sdk/remote/utils.js +++ /dev/null @@ -1,39 +0,0 @@ -/* 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 { List, addListItem, removeListItem } = require('../util/list'); -const { emit } = require('../event/core'); -const { pipe } = require('../event/utils'); - -// A helper class that maintains a list of EventTargets. Any events emitted -// to an EventTarget are also emitted by the EventParent. Likewise for an -// EventTarget's port property. -const EventParent = Class({ - implements: [ List ], - - attachItem: function(item) { - addListItem(this, item); - - pipe(item.port, this.port); - pipe(item, this); - - item.once('detach', () => { - removeListItem(this, item); - }) - - emit(this, 'attach', item); - }, - - // Calls listener for every object already in the list and every object - // subsequently added to the list. - forEvery: function(listener) { - for (let item of this) - listener(item); - - this.on('attach', listener); - } -}); -exports.EventParent = EventParent; diff --git a/addon-sdk/source/lib/sdk/request.js b/addon-sdk/source/lib/sdk/request.js deleted file mode 100644 index 96bb1e6d7..000000000 --- a/addon-sdk/source/lib/sdk/request.js +++ /dev/null @@ -1,248 +0,0 @@ -/* 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": "stable" -}; - -const { ns } = require("./core/namespace"); -const { emit } = require("./event/core"); -const { merge } = require("./util/object"); -const { stringify } = require("./querystring"); -const { EventTarget } = require("./event/target"); -const { Class } = require("./core/heritage"); -const { XMLHttpRequest, forceAllowThirdPartyCookie } = require("./net/xhr"); -const apiUtils = require("./deprecated/api-utils"); -const { isValidURI } = require("./url.js"); - -const response = ns(); -const request = ns(); - -// Instead of creating a new validator for each request, just make one and -// reuse it. -const { validateOptions, validateSingleOption } = new OptionsValidator({ - url: { - // Also converts a URL instance to string, bug 857902 - map: url => url.toString(), - ok: isValidURI - }, - headers: { - map: v => v || {}, - is: ["object"], - }, - content: { - map: v => v || null, - is: ["string", "object", "null"], - }, - contentType: { - map: v => v || "application/x-www-form-urlencoded", - is: ["string"], - }, - overrideMimeType: { - map: v => v || null, - is: ["string", "null"], - }, - anonymous: { - map: v => v || false, - is: ["boolean", "null"], - } -}); - -const REUSE_ERROR = "This request object has been used already. You must " + - "create a new one to make a new request." - -// Utility function to prep the request since it's the same between -// request types -function runRequest(mode, target) { - let source = request(target) - let { xhr, url, content, contentType, headers, overrideMimeType, anonymous } = source; - - let isGetOrHead = (mode == "GET" || mode == "HEAD"); - - // If this request has already been used, then we can't reuse it. - // Throw an error. - if (xhr) - throw new Error(REUSE_ERROR); - - xhr = source.xhr = new XMLHttpRequest({ - mozAnon: anonymous - }); - - // Build the data to be set. For GET or HEAD requests, we want to append that - // to the URL before opening the request. - let data = stringify(content); - // If the URL already has ? in it, then we want to just use & - if (isGetOrHead && data) - url = url + (/\?/.test(url) ? "&" : "?") + data; - - // open the request - xhr.open(mode, url); - - - forceAllowThirdPartyCookie(xhr); - - // request header must be set after open, but before send - xhr.setRequestHeader("Content-Type", contentType); - - // set other headers - Object.keys(headers).forEach(function(name) { - xhr.setRequestHeader(name, headers[name]); - }); - - // set overrideMimeType - if (overrideMimeType) - xhr.overrideMimeType(overrideMimeType); - - // handle the readystate, create the response, and call the callback - xhr.onreadystatechange = function onreadystatechange() { - if (xhr.readyState === 4) { - let response = Response(xhr); - source.response = response; - emit(target, 'complete', response); - } - }; - - // actually send the request. - // We don't want to send data on GET or HEAD requests. - xhr.send(!isGetOrHead ? data : null); -} - -const Request = Class({ - extends: EventTarget, - initialize: function initialize(options) { - // `EventTarget.initialize` will set event listeners that are named - // like `onEvent` in this case `onComplete` listener will be set to - // `complete` event. - EventTarget.prototype.initialize.call(this, options); - - // Copy normalized options. - merge(request(this), validateOptions(options)); - }, - get url() { return request(this).url; }, - set url(value) { request(this).url = validateSingleOption('url', value); }, - get headers() { return request(this).headers; }, - set headers(value) { - return request(this).headers = validateSingleOption('headers', value); - }, - get content() { return request(this).content; }, - set content(value) { - request(this).content = validateSingleOption('content', value); - }, - get contentType() { return request(this).contentType; }, - set contentType(value) { - request(this).contentType = validateSingleOption('contentType', value); - }, - get anonymous() { return request(this).anonymous; }, - get response() { return request(this).response; }, - delete: function() { - runRequest('DELETE', this); - return this; - }, - get: function() { - runRequest('GET', this); - return this; - }, - post: function() { - runRequest('POST', this); - return this; - }, - put: function() { - runRequest('PUT', this); - return this; - }, - head: function() { - runRequest('HEAD', this); - return this; - } -}); -exports.Request = Request; - -const Response = Class({ - initialize: function initialize(request) { - response(this).request = request; - }, - // more about responseURL: https://bugzilla.mozilla.org/show_bug.cgi?id=998076 - get url() { - return response(this).request.responseURL; - }, - get text() { - return response(this).request.responseText; - }, - get xml() { - throw new Error("Sorry, the 'xml' property is no longer available. " + - "see bug 611042 for more information."); - }, - get status() { - return response(this).request.status; - }, - get statusText() { - return response(this).request.statusText; - }, - get json() { - try { - return JSON.parse(this.text); - } catch(error) { - return null; - } - }, - get headers() { - let headers = {}, lastKey; - // Since getAllResponseHeaders() will return null if there are no headers, - // defend against it by defaulting to "" - let rawHeaders = response(this).request.getAllResponseHeaders() || ""; - rawHeaders.split("\n").forEach(function (h) { - // According to the HTTP spec, the header string is terminated by an empty - // line, so we can just skip it. - if (!h.length) { - return; - } - - let index = h.indexOf(":"); - // The spec allows for leading spaces, so instead of assuming a single - // leading space, just trim the values. - let key = h.substring(0, index).trim(), - val = h.substring(index + 1).trim(); - - // For empty keys, that means that the header value spanned multiple lines. - // In that case we should append the value to the value of lastKey with a - // new line. We'll assume lastKey will be set because there should never - // be an empty key on the first pass. - if (key) { - headers[key] = val; - lastKey = key; - } - else { - headers[lastKey] += "\n" + val; - } - }); - return headers; - }, - get anonymous() { - return response(this).request.mozAnon; - } -}); - -// apiUtils.validateOptions doesn't give the ability to easily validate single -// options, so this is a wrapper that provides that ability. -function OptionsValidator(rules) { - return { - validateOptions: function (options) { - return apiUtils.validateOptions(options, rules); - }, - validateSingleOption: function (field, value) { - // We need to create a single rule object from our listed rules. To avoid - // JavaScript String warnings, check for the field & default to an empty object. - let singleRule = {}; - if (field in rules) { - singleRule[field] = rules[field]; - } - let singleOption = {}; - singleOption[field] = value; - // This should throw if it's invalid, which will bubble up & out. - return apiUtils.validateOptions(singleOption, singleRule)[field]; - } - }; -} diff --git a/addon-sdk/source/lib/sdk/selection.js b/addon-sdk/source/lib/sdk/selection.js deleted file mode 100644 index 8682e8c6d..000000000 --- a/addon-sdk/source/lib/sdk/selection.js +++ /dev/null @@ -1,470 +0,0 @@ -/* 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": "stable", - "engines": { - "Firefox": "*", - "SeaMonkey": "*" - } -}; - -const { Ci, Cc } = require("chrome"), - { setTimeout } = require("./timers"), - { emit, off } = require("./event/core"), - { Class, obscure } = require("./core/heritage"), - { EventTarget } = require("./event/target"), - { ns } = require("./core/namespace"), - { when: unload } = require("./system/unload"), - { ignoreWindow } = require('./private-browsing/utils'), - { getTabs, getTabForContentWindow, - getAllTabContentWindows } = require('./tabs/utils'), - winUtils = require("./window/utils"), - events = require("./system/events"); - -// The selection types -const HTML = 0x01, - TEXT = 0x02, - DOM = 0x03; // internal use only - -// A more developer-friendly message than the caught exception when is not -// possible change a selection. -const ERR_CANNOT_CHANGE_SELECTION = - "It isn't possible to change the selection, as there isn't currently a selection"; - -const selections = ns(); - -const Selection = Class({ - /** - * Creates an object from which a selection can be set, get, etc. Each - * object has an associated with a range number. Range numbers are the - * 0-indexed counter of selection ranges as explained at - * https://developer.mozilla.org/en/DOM/Selection. - * - * @param rangeNumber - * The zero-based range index into the selection - */ - initialize: function initialize(rangeNumber) { - // In order to hide the private `rangeNumber` argument from API consumers - // while still enabling Selection getters/setters to access it, we define - // it as non enumerable, non configurable property. While consumers still - // may discover it they won't be able to do any harm which is good enough - // in this case. - Object.defineProperties(this, { - rangeNumber: { - enumerable: false, - configurable: false, - value: rangeNumber - } - }); - }, - get text() { return getSelection(TEXT, this.rangeNumber); }, - set text(value) { setSelection(TEXT, value, this.rangeNumber); }, - get html() { return getSelection(HTML, this.rangeNumber); }, - set html(value) { setSelection(HTML, value, this.rangeNumber); }, - get isContiguous() { - - // If there are multiple non empty ranges, the selection is definitely - // discontiguous. It returns `false` also if there are no valid selection. - let count = 0; - for (let sel in selectionIterator) - if (++count > 1) - break; - - return count === 1; - } -}); - -const selectionListener = { - notifySelectionChanged: function (document, selection, reason) { - if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(type => reason & - Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "") - return; - - this.onSelect(); - }, - - onSelect: function() { - emit(module.exports, "select"); - } -} - -/** - * Defines iterators so that discontiguous selections can be iterated. - * Empty selections are skipped - see `safeGetRange` for further details. - * - * If discontiguous selections are in a text field, only the first one - * is returned because the text field selection APIs doesn't support - * multiple selections. - */ -function* forOfIterator() { - let selection = getSelection(DOM); - let count = 0; - - if (selection) - count = selection.rangeCount || (getElementWithSelection() ? 1 : 0); - - for (let i = 0; i < count; i++) { - let sel = Selection(i); - - if (sel.text) - yield Selection(i); - } -} - -const selectionIteratorOptions = { - __iterator__: function() { - for (let item of this) - yield item; - } -} -selectionIteratorOptions[Symbol.iterator] = forOfIterator; -const selectionIterator = obscure(selectionIteratorOptions); - -/** - * Returns the most recent focused window. - * if private browsing window is most recent and not supported, - * then ignore it and return `null`, because the focused window - * can't be targeted. - */ -function getFocusedWindow() { - let window = winUtils.getFocusedWindow(); - - return ignoreWindow(window) ? null : window; -} - -/** - * Returns the focused element in the most recent focused window - * if private browsing window is most recent and not supported, - * then ignore it and return `null`, because the focused element - * can't be targeted. - */ -function getFocusedElement() { - let element = winUtils.getFocusedElement(); - - if (!element || ignoreWindow(element.ownerDocument.defaultView)) - return null; - - return element; -} - -/** - * Returns the current selection from most recent content window. Depending on - * the specified |type|, the value returned can be a string of text, stringified - * HTML, or a DOM selection object as described at - * https://developer.mozilla.org/en/DOM/Selection. - * - * @param type - * Specifies the return type of the selection. Valid values are the one - * of the constants HTML, TEXT, or DOM. - * - * @param rangeNumber - * Specifies the zero-based range index of the returned selection. - */ -function getSelection(type, rangeNumber) { - let window, selection; - try { - window = getFocusedWindow(); - selection = window.getSelection(); - } - catch (e) { - return null; - } - - // Get the selected content as the specified type - if (type == DOM) { - return selection; - } - else if (type == TEXT) { - let range = safeGetRange(selection, rangeNumber); - - if (range) - return range.toString(); - - let node = getElementWithSelection(); - - if (!node) - return null; - - return node.value.substring(node.selectionStart, node.selectionEnd); - } - else if (type == HTML) { - let range = safeGetRange(selection, rangeNumber); - // Another way, but this includes the xmlns attribute for all elements in - // Gecko 1.9.2+ : - // return Cc["@mozilla.org/xmlextras/xmlserializer;1"]. - // createInstance(Ci.nsIDOMSerializer).serializeToSTring(range. - // cloneContents()); - if (!range) - return null; - - let node = window.document.createElement("span"); - node.appendChild(range.cloneContents()); - return node.innerHTML; - } - - throw new Error("Type " + type + " is unrecognized."); -} - -/** - * Sets the current selection of the most recent content document by changing - * the existing selected text/HTML range to the specified value. - * - * @param val - * The value for the new selection - * - * @param rangeNumber - * The zero-based range index of the selection to be set - * - */ -function setSelection(type, val, rangeNumber) { - // Make sure we have a window context & that there is a current selection. - // Selection cannot be set unless there is an existing selection. - let window, selection; - - try { - window = getFocusedWindow(); - selection = window.getSelection(); - } - catch (e) { - throw new Error(ERR_CANNOT_CHANGE_SELECTION); - } - - let range = safeGetRange(selection, rangeNumber); - - if (range) { - let fragment; - - if (type === HTML) - fragment = range.createContextualFragment(val); - else { - fragment = range.createContextualFragment(""); - fragment.textContent = val; - } - - range.deleteContents(); - range.insertNode(fragment); - } - else { - let node = getElementWithSelection(); - - if (!node) - throw new Error(ERR_CANNOT_CHANGE_SELECTION); - - let { value, selectionStart, selectionEnd } = node; - - let newSelectionEnd = selectionStart + val.length; - - node.value = value.substring(0, selectionStart) + - val + - value.substring(selectionEnd, value.length); - - node.setSelectionRange(selectionStart, newSelectionEnd); - } -} - -/** - * Returns the specified range in a selection without throwing an exception. - * - * @param selection - * A selection object as described at - * https://developer.mozilla.org/en/DOM/Selection - * - * @param [rangeNumber] - * Specifies the zero-based range index of the returned selection. - * If it's not provided the function will return the first non empty - * range, if any. - */ -function safeGetRange(selection, rangeNumber) { - try { - let { rangeCount } = selection; - let range = null; - - if (typeof rangeNumber === "undefined") - rangeNumber = 0; - else - rangeCount = rangeNumber + 1; - - for (; rangeNumber < rangeCount; rangeNumber++ ) { - range = selection.getRangeAt(rangeNumber); - - if (range && range.toString()) - break; - - range = null; - } - - return range; - } - catch (e) { - return null; - } -} - -/** - * Returns a reference of the DOM's active element for the window given, if it - * supports the text field selection API and has a text selected. - * - * Note: - * we need this method because window.getSelection doesn't return a selection - * for text selected in a form field (see bug 85686) - */ -function getElementWithSelection() { - let element = getFocusedElement(); - - if (!element) - return null; - - try { - // Accessing selectionStart and selectionEnd on e.g. a button - // results in an exception thrown as per the HTML5 spec. See - // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection - - let { value, selectionStart, selectionEnd } = element; - - let hasSelection = typeof value === "string" && - !isNaN(selectionStart) && - !isNaN(selectionEnd) && - selectionStart !== selectionEnd; - - return hasSelection ? element : null; - } - catch (err) { - return null; - } - -} - -/** - * Adds the Selection Listener to the content's window given - */ -function addSelectionListener(window) { - let selection = window.getSelection(); - - // Don't add the selection's listener more than once to the same window, - // if the selection object is the same - if ("selection" in selections(window) && selections(window).selection === selection) - return; - - // We ensure that the current selection is an instance of - // `nsISelectionPrivate` before working on it, in case is `null`. - // - // If it's `null` it's likely too early to add the listener, and we demand - // that operation to `document-shown` - it can easily happens for frames - if (selection instanceof Ci.nsISelectionPrivate) - selection.addSelectionListener(selectionListener); - - // nsISelectionListener implementation seems not fire a notification if - // a selection is in a text field, therefore we need to add a listener to - // window.onselect, that is fired only for text fields. - // For consistency, we add it only when the nsISelectionListener is added. - // - // https://developer.mozilla.org/en/DOM/window.onselect - window.addEventListener("select", selectionListener.onSelect, true); - - selections(window).selection = selection; -}; - -/** - * Removes the Selection Listener to the content's window given - */ -function removeSelectionListener(window) { - // Don't remove the selection's listener to a window that wasn't handled. - if (!("selection" in selections(window))) - return; - - let selection = window.getSelection(); - let isSameSelection = selection === selections(window).selection; - - // Before remove the listener, we ensure that the current selection is an - // instance of `nsISelectionPrivate` (it could be `null`), and that is still - // the selection we managed for this window (it could be detached). - if (selection instanceof Ci.nsISelectionPrivate && isSameSelection) - selection.removeSelectionListener(selectionListener); - - window.removeEventListener("select", selectionListener.onSelect, true); - - delete selections(window).selection; -}; - -function onContent(event) { - let window = event.subject.defaultView; - - // We are not interested in documents without valid defaultView (e.g. XML) - // that aren't in a tab (e.g. Panel); or in private windows - if (window && getTabForContentWindow(window) && !ignoreWindow(window)) { - addSelectionListener(window); - } -} - -// Adds Selection listener to new documents -// Note that strong reference is needed for documents that are loading slowly or -// where the server didn't close the connection (e.g. "comet"). -events.on("document-element-inserted", onContent, true); - -// Adds Selection listeners to existing documents -getAllTabContentWindows().forEach(addSelectionListener); - -// When a document is not visible anymore the selection object is detached, and -// a new selection object is created when it becomes visible again. -// That makes the previous selection's listeners added previously totally -// useless – the listeners are not notified anymore. -// To fix that we're listening for `document-shown` event in order to add -// the listeners to the new selection object created. -// -// See bug 665386 for further details. - -function onShown(event) { - let window = event.subject.defaultView; - - // We are not interested in documents without valid defaultView. - // For example XML documents don't have windows and we don't yet support them. - if (!window) - return; - - // We want to handle only the windows where we added selection's listeners - if ("selection" in selections(window)) { - let currentSelection = window.getSelection(); - let { selection } = selections(window); - - // If the current selection for the window given is different from the one - // stored in the namespace, we need to add the listeners again, and replace - // the previous selection in our list with the new one. - // - // Notice that we don't have to remove the listeners from the old selection, - // because is detached. An attempt to remove the listener, will raise an - // error (see http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#5343 ) - // - // We ensure that the current selection is an instance of - // `nsISelectionPrivate` before working on it, in case is `null`. - if (currentSelection instanceof Ci.nsISelectionPrivate && - currentSelection !== selection) { - - window.addEventListener("select", selectionListener.onSelect, true); - currentSelection.addSelectionListener(selectionListener); - selections(window).selection = currentSelection; - } - } -} - -events.on("document-shown", onShown, true); - -// Removes Selection listeners when the add-on is unloaded -unload(function(){ - getAllTabContentWindows().forEach(removeSelectionListener); - - events.off("document-element-inserted", onContent); - events.off("document-shown", onShown); - - off(exports); -}); - -const selection = Class({ - extends: EventTarget, - implements: [ Selection, selectionIterator ] -})(); - -module.exports = selection; diff --git a/addon-sdk/source/lib/sdk/self.js b/addon-sdk/source/lib/sdk/self.js deleted file mode 100644 index c2114a926..000000000 --- a/addon-sdk/source/lib/sdk/self.js +++ /dev/null @@ -1,61 +0,0 @@ -/* 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": "stable" -}; - -const { CC } = require('chrome'); -const options = require('@loader/options'); - -const { get } = require("./preferences/service"); -const { readURISync } = require('./net/url'); - -const id = options.id; - -const readPref = key => get("extensions." + id + ".sdk." + key); - -const name = readPref("name") || options.name; -const version = readPref("version") || options.version; -const loadReason = readPref("load.reason") || options.loadReason; -const rootURI = readPref("rootURI") || options.rootURI || ""; -const baseURI = readPref("baseURI") || options.prefixURI + name + "/" -const addonDataURI = baseURI + "data/"; -const metadata = options.metadata || {}; -const permissions = metadata.permissions || {}; -const isPacked = rootURI && rootURI.indexOf("jar:") === 0; - -const isPrivateBrowsingSupported = 'private-browsing' in permissions && - permissions['private-browsing'] === true; - -const uri = (path="") => - path.includes(":") ? path : addonDataURI + path.replace(/^\.\//, ""); - -var preferencesBranch = ("preferences-branch" in metadata) - ? metadata["preferences-branch"] - : options.preferencesBranch - -if (/[^\w{@}.-]/.test(preferencesBranch)) { - preferencesBranch = id; - console.warn("Ignoring preferences-branch (not a valid branch name)"); -} - -// Some XPCOM APIs require valid URIs as an argument for certain operations -// (see `nsILoginManager` for example). This property represents add-on -// associated unique URI string that can be used for that. -exports.uri = 'addon:' + id; -exports.id = id; -exports.preferencesBranch = preferencesBranch || id; -exports.name = name; -exports.loadReason = loadReason; -exports.version = version; -exports.packed = isPacked; -exports.data = Object.freeze({ - url: uri, - load: function read(path) { - return readURISync(uri(path)); - } -}); -exports.isPrivateBrowsingSupported = isPrivateBrowsingSupported; diff --git a/addon-sdk/source/lib/sdk/simple-prefs.js b/addon-sdk/source/lib/sdk/simple-prefs.js deleted file mode 100644 index 3472f4418..000000000 --- a/addon-sdk/source/lib/sdk/simple-prefs.js +++ /dev/null @@ -1,26 +0,0 @@ -/* 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": "experimental" -}; - -const { emit, off } = require("./event/core"); -const { PrefsTarget } = require("./preferences/event-target"); -const { preferencesBranch, id } = require("./self"); -const { on } = require("./system/events"); - -const ADDON_BRANCH = "extensions." + preferencesBranch + "."; -const BUTTON_PRESSED = id + "-cmdPressed"; - -const target = PrefsTarget({ branchName: ADDON_BRANCH }); - -// Listen to clicks on buttons -function buttonClick({ data }) { - emit(target, data); -} -on(BUTTON_PRESSED, buttonClick); - -module.exports = target; diff --git a/addon-sdk/source/lib/sdk/simple-storage.js b/addon-sdk/source/lib/sdk/simple-storage.js deleted file mode 100644 index bcf9b1cb9..000000000 --- a/addon-sdk/source/lib/sdk/simple-storage.js +++ /dev/null @@ -1,235 +0,0 @@ -/* 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": "stable" -}; - -const { Cc, Ci } = require("chrome"); -const file = require("./io/file"); -const prefs = require("./preferences/service"); -const jpSelf = require("./self"); -const timer = require("./timers"); -const unload = require("./system/unload"); -const { emit, on, off } = require("./event/core"); - -const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod"; -const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes - -const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota"; -const QUOTA_DEFAULT = 5242880; // 5 MiB - -const JETPACK_DIR_BASENAME = "jetpack"; - -Object.defineProperties(exports, { - storage: { - enumerable: true, - get: function() { return manager.root; }, - set: function(value) { manager.root = value; } - }, - quotaUsage: { - get: function() { return manager.quotaUsage; } - } -}); - -// A generic JSON store backed by a file on disk. This should be isolated -// enough to move to its own module if need be... -function JsonStore(options) { - this.filename = options.filename; - this.quota = options.quota; - this.writePeriod = options.writePeriod; - this.onOverQuota = options.onOverQuota; - this.onWrite = options.onWrite; - - unload.ensure(this); - - this.writeTimer = timer.setInterval(this.write.bind(this), - this.writePeriod); -} - -JsonStore.prototype = { - // The store's root. - get root() { - return this.isRootInited ? this._root : {}; - }, - - // Performs some type checking. - set root(val) { - let types = ["array", "boolean", "null", "number", "object", "string"]; - if (types.indexOf(typeof(val)) < 0) { - throw new Error("storage must be one of the following types: " + - types.join(", ")); - } - this._root = val; - return val; - }, - - // True if the root has ever been set (either via the root setter or by the - // backing file's having been read). - get isRootInited() { - return this._root !== undefined; - }, - - // Percentage of quota used, as a number [0, Inf). > 1 implies over quota. - // Undefined if there is no quota. - get quotaUsage() { - return this.quota > 0 ? - JSON.stringify(this.root).length / this.quota : - undefined; - }, - - // Removes the backing file and all empty subdirectories. - purge: function JsonStore_purge() { - try { - // This'll throw if the file doesn't exist. - file.remove(this.filename); - let parentPath = this.filename; - do { - parentPath = file.dirname(parentPath); - // This'll throw if the dir isn't empty. - file.rmdir(parentPath); - } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME); - } - catch (err) {} - }, - - // Initializes the root by reading the backing file. - read: function JsonStore_read() { - try { - let str = file.read(this.filename); - - // Ideally we'd log the parse error with console.error(), but logged - // errors cause tests to fail. Supporting "known" errors in the test - // harness appears to be non-trivial. Maybe later. - this.root = JSON.parse(str); - } - catch (err) { - this.root = {}; - } - }, - - // If the store is under quota, writes the root to the backing file. - // Otherwise quota observers are notified and nothing is written. - write: function JsonStore_write() { - if (this.quotaUsage > 1) - this.onOverQuota(this); - else - this._write(); - }, - - // Cleans up on unload. If unloading because of uninstall, the store is - // purged; otherwise it's written. - unload: function JsonStore_unload(reason) { - timer.clearInterval(this.writeTimer); - this.writeTimer = null; - - if (reason === "uninstall") - this.purge(); - else - this._write(); - }, - - // True if the root is an empty object. - get _isEmpty() { - if (this.root && typeof(this.root) === "object") { - let empty = true; - for (let key in this.root) { - empty = false; - break; - } - return empty; - } - return false; - }, - - // Writes the root to the backing file, notifying write observers when - // complete. If the store is over quota or if it's empty and the store has - // never been written, nothing is written and write observers aren't notified. - _write: function JsonStore__write() { - // Don't write if the root is uninitialized or if the store is empty and the - // backing file doesn't yet exist. - if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename))) - return; - - // If the store is over quota, don't write. The current under-quota state - // should persist. - if (this.quotaUsage > 1) - return; - - // Finally, write. - let stream = file.open(this.filename, "w"); - try { - stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) { - if (err) - console.error("Error writing simple storage file: " + this.filename); - else if (this.onWrite) - this.onWrite(this); - }.bind(this)); - } - catch (err) { - // writeAsync closes the stream after it's done, so only close on error. - stream.close(); - } - } -}; - - -// This manages a JsonStore singleton and tailors its use to simple storage. -// The root of the JsonStore is lazy-loaded: The backing file is only read the -// first time the root's gotten. -var manager = ({ - jsonStore: null, - - // The filename of the store, based on the profile dir and extension ID. - get filename() { - let storeFile = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties). - get("ProfD", Ci.nsIFile); - storeFile.append(JETPACK_DIR_BASENAME); - storeFile.append(jpSelf.id); - storeFile.append("simple-storage"); - file.mkpath(storeFile.path); - storeFile.append("store.json"); - return storeFile.path; - }, - - get quotaUsage() { - return this.jsonStore.quotaUsage; - }, - - get root() { - if (!this.jsonStore.isRootInited) - this.jsonStore.read(); - return this.jsonStore.root; - }, - - set root(val) { - return this.jsonStore.root = val; - }, - - unload: function manager_unload() { - off(this); - }, - - new: function manager_constructor() { - let manager = Object.create(this); - unload.ensure(manager); - - manager.jsonStore = new JsonStore({ - filename: manager.filename, - writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT), - quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT), - onOverQuota: emit.bind(null, exports, "OverQuota") - }); - - return manager; - } -}).new(); - -exports.on = on.bind(null, exports); -exports.removeListener = function(type, listener) { - off(exports, type, listener); -}; diff --git a/addon-sdk/source/lib/sdk/stylesheet/style.js b/addon-sdk/source/lib/sdk/stylesheet/style.js deleted file mode 100644 index 7ec0787e1..000000000 --- a/addon-sdk/source/lib/sdk/stylesheet/style.js +++ /dev/null @@ -1,71 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci } = require("chrome"); -const { Class } = require("../core/heritage"); -const { URL, isLocalURL } = require('../url'); -const events = require("../system/events"); -const { loadSheet, removeSheet, isTypeValid } = require("./utils"); -const { isString } = require("../lang/type"); -const { attachTo, detachFrom } = require("../content/mod"); -const { data } = require('../self'); - -const { freeze, create } = Object; - -function Style({ source, uri, type }) { - source = source == null ? null : freeze([].concat(source)); - uri = uri == null ? null : freeze([].concat(uri)); - type = type == null ? "author" : type; - - if (source && !source.every(isString)) - throw new Error('Style.source must be a string or an array of strings.'); - - if (uri && !uri.every(isLocalURL)) - throw new Error('Style.uri must be a local URL or an array of local URLs'); - - if (type && !isTypeValid(type)) - throw new Error('Style.type must be "agent", "user" or "author"'); - - return freeze(create(Style.prototype, { - "source": { value: source, enumerable: true }, - "uri": { value: uri, enumerable: true }, - "type": { value: type, enumerable: true } - })); -}; - -exports.Style = Style; - -attachTo.define(Style, function (style, window) { - if (style.uri) { - for (let uri of style.uri) - loadSheet(window, data.url(uri), style.type); - } - - if (style.source) { - let uri = "data:text/css;charset=utf-8,"; - - uri += encodeURIComponent(style.source.join("")); - - loadSheet(window, uri, style.type); - } -}); - -detachFrom.define(Style, function (style, window) { - if (style.uri) - for (let uri of style.uri) - removeSheet(window, data.url(uri)); - - if (style.source) { - let uri = "data:text/css;charset=utf-8,"; - - uri += encodeURIComponent(style.source.join("")); - - removeSheet(window, uri, style.type); - } -}); diff --git a/addon-sdk/source/lib/sdk/stylesheet/utils.js b/addon-sdk/source/lib/sdk/stylesheet/utils.js deleted file mode 100644 index 844996bf3..000000000 --- a/addon-sdk/source/lib/sdk/stylesheet/utils.js +++ /dev/null @@ -1,75 +0,0 @@ -/* 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": "experimental" -}; - -const { Ci } = require("chrome"); - -const SHEET_TYPE = { - "agent": "AGENT_SHEET", - "user": "USER_SHEET", - "author": "AUTHOR_SHEET" -}; - -function getDOMWindowUtils(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils); -}; - -/** - * Synchronously loads a style sheet from `uri` and adds it to the list of - * additional style sheets of the document. - * The sheets added takes effect immediately, and only on the document of the - * `window` given. - */ -function loadSheet(window, url, type) { - if (!(type && type in SHEET_TYPE)) - type = "author"; - - type = SHEET_TYPE[type]; - - if (url instanceof Ci.nsIURI) - url = url.spec; - - let winUtils = getDOMWindowUtils(window); - try { - winUtils.loadSheetUsingURIString(url, winUtils[type]); - } - catch (e) {}; -}; -exports.loadSheet = loadSheet; - -/** - * Remove the document style sheet at `sheetURI` from the list of additional - * style sheets of the document. The removal takes effect immediately. - */ -function removeSheet(window, url, type) { - if (!(type && type in SHEET_TYPE)) - type = "author"; - - type = SHEET_TYPE[type]; - - if (url instanceof Ci.nsIURI) - url = url.spec; - - let winUtils = getDOMWindowUtils(window); - - try { - winUtils.removeSheetUsingURIString(url, winUtils[type]); - } - catch (e) {}; -}; -exports.removeSheet = removeSheet; - -/** - * Returns `true` if the `type` given is valid, otherwise `false`. - * The values currently accepted are: "agent", "user" and "author". - */ -function isTypeValid(type) { - return type in SHEET_TYPE; -} -exports.isTypeValid = isTypeValid; diff --git a/addon-sdk/source/lib/sdk/system.js b/addon-sdk/source/lib/sdk/system.js deleted file mode 100644 index 1acfe8c8c..000000000 --- a/addon-sdk/source/lib/sdk/system.js +++ /dev/null @@ -1,172 +0,0 @@ -/* 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 { Cc, Ci, CC } = require('chrome'); -const options = require('@loader/options'); -const runtime = require("./system/runtime"); -const { when: unload } = require("./system/unload"); - -const appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. - getService(Ci.nsIAppStartup); -const appInfo = Cc["@mozilla.org/xre/app-info;1"]. - getService(Ci.nsIXULAppInfo); -const directoryService = Cc['@mozilla.org/file/directory_service;1']. - getService(Ci.nsIProperties); - -const PR_WRONLY = parseInt("0x02"); -const PR_CREATE_FILE = parseInt("0x08"); -const PR_APPEND = parseInt("0x10"); -const PR_TRUNCATE = parseInt("0x20"); - -function openFile(path, mode) { - let file = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - file.initWithPath(path); - let stream = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - stream.init(file, mode, -1, 0); - return stream -} - -const { eAttemptQuit: E_ATTEMPT, eForceQuit: E_FORCE } = appStartup; - -/** - * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"` - */ -exports.staticArgs = options.staticArgs; - -/** - * Environment variables. Environment variables are non-enumerable properties - * of this object (key is name and value is value). - */ -exports.env = require('./system/environment').env; - -/** - * Ends the process with the specified `code`. If omitted, exit uses the - * 'success' code 0. To exit with failure use `1`. - * TODO: Improve platform to actually quit with an exit code. - */ -var forcedExit = false; -exports.exit = function exit(code) { - if (forcedExit) { - // a forced exit was already tried - // NOTE: exit(0) is called twice sometimes (ex when using cfx testaddons) - return; - } - - let resultsFile = 'resultFile' in options && options.resultFile; - function unloader() { - if (!options.resultFile) { - return; - } - - // This is used by 'cfx' to find out exit code. - let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; - let stream = openFile(options.resultFile, mode); - let status = code ? 'FAIL' : 'OK'; - stream.write(status, status.length); - stream.flush(); - stream.close(); - return; - } - - if (code == 0) { - forcedExit = true; - } - - // Bug 856999: Prevent automatic kill of Firefox when running tests - if (options.noQuit) { - return unload(unloader); - } - - unloader(); - appStartup.quit(code ? E_ATTEMPT : E_FORCE); -}; - -// Adapter for nodejs's stdout & stderr: -// http://nodejs.org/api/process.html#process_process_stdout -var stdout = Object.freeze({ write: dump, end: dump }); -exports.stdout = stdout; -exports.stderr = stdout; - -/** - * Returns a path of the system's or application's special directory / file - * associated with a given `id`. For list of possible `id`s please see: - * https://developer.mozilla.org/en-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories - * http://dxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h - * @example - * - * // get firefox profile path - * let profilePath = require('system').pathFor('ProfD'); - * // get OS temp files directory (/tmp) - * let temps = require('system').pathFor('TmpD'); - * // get OS desktop path for an active user (~/Desktop on linux - * // or C:\Documents and Settings\username\Desktop on windows). - * let desktopPath = require('system').pathFor('Desk'); - */ -exports.pathFor = function pathFor(id) { - return directoryService.get(id, Ci.nsIFile).path; -}; - -/** - * What platform you're running on (all lower case string). - * For possible values see: - * https://developer.mozilla.org/en/OS_TARGET - */ -exports.platform = runtime.OS.toLowerCase(); - -const [, architecture, compiler] = runtime.XPCOMABI ? - runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) : - [, null, null]; - -/** - * What processor architecture you're running on: - * `'arm', 'ia32', or 'x64'`. - */ -exports.architecture = architecture; - -/** - * What compiler used for build: - * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...` - */ -exports.compiler = compiler; - -/** - * The application's build ID/date, for example "2004051604". - */ -exports.build = appInfo.appBuildID; - -/** - * The XUL application's UUID. - * This has traditionally been in the form - * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may - * be: "appname@vendor.tld". - */ -exports.id = appInfo.ID; - -/** - * The name of the application. - */ -exports.name = appInfo.name; - -/** - * The XUL application's version, for example "0.8.0+" or "3.7a1pre". - */ -exports.version = appInfo.version; - -/** - * XULRunner version. - */ -exports.platformVersion = appInfo.platformVersion; - - -/** - * The name of the application vendor, for example "Mozilla". - */ -exports.vendor = appInfo.vendor; diff --git a/addon-sdk/source/lib/sdk/system/child_process.js b/addon-sdk/source/lib/sdk/system/child_process.js deleted file mode 100644 index 8ea1f4f80..000000000 --- a/addon-sdk/source/lib/sdk/system/child_process.js +++ /dev/null @@ -1,332 +0,0 @@ -/* 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': 'experimental' -}; - -var { Ci } = require('chrome'); -var subprocess = require('./child_process/subprocess'); -var { EventTarget } = require('../event/target'); -var { Stream } = require('../io/stream'); -var { on, emit, off } = require('../event/core'); -var { Class } = require('../core/heritage'); -var { platform } = require('../system'); -var { isFunction, isArray } = require('../lang/type'); -var { delay } = require('../lang/functional'); -var { merge } = require('../util/object'); -var { setTimeout, clearTimeout } = require('../timers'); -var isWindows = platform.indexOf('win') === 0; - -var processes = new WeakMap(); - - -/** - * The `Child` class wraps a subprocess command, exposes - * the stdio streams, and methods to manipulate the subprocess - */ -var Child = Class({ - implements: [EventTarget], - initialize: function initialize (options) { - let child = this; - let proc; - - this.killed = false; - this.exitCode = undefined; - this.signalCode = undefined; - - this.stdin = Stream(); - this.stdout = Stream(); - this.stderr = Stream(); - - try { - proc = subprocess.call({ - command: options.file, - arguments: options.cmdArgs, - environment: serializeEnv(options.env), - workdir: options.cwd, - charset: options.encoding, - stdout: data => emit(child.stdout, 'data', data), - stderr: data => emit(child.stderr, 'data', data), - stdin: stream => { - child.stdin.on('data', pumpStdin); - child.stdin.on('end', function closeStdin () { - child.stdin.off('data', pumpStdin); - child.stdin.off('end', closeStdin); - stream.close(); - }); - function pumpStdin (data) { - stream.write(data); - } - }, - done: function (result, error) { - if (error) - return handleError(error); - - // Only emit if child is not killed; otherwise, - // the `kill` method will handle this - if (!child.killed) { - child.exitCode = result.exitCode; - child.signalCode = null; - - // If process exits with < 0, there was an error - if (child.exitCode < 0) { - handleError(new Error('Process exited with exit code ' + child.exitCode)); - } - else { - // Also do 'exit' event as there's not much of - // a difference in our implementation as we're not using - // node streams - emit(child, 'exit', child.exitCode, child.signalCode); - } - - // Emit 'close' event with exit code and signal, - // which is `null`, as it was not a killed process - emit(child, 'close', child.exitCode, child.signalCode); - } - } - }); - processes.set(child, proc); - } catch (e) { - // Delay the error handling so an error handler can be set - // during the same tick that the Child was created - delay(() => handleError(e)); - } - - // `handleError` is called when process could not even - // be spawned - function handleError (e) { - // If error is an nsIObject, make a fresh error object - // so we're not exposing nsIObjects, and we can modify it - // with additional process information, like node - let error = e; - if (e instanceof Ci.nsISupports) { - error = new Error(e.message, e.filename, e.lineNumber); - } - emit(child, 'error', error); - child.exitCode = -1; - child.signalCode = null; - emit(child, 'close', child.exitCode, child.signalCode); - } - }, - kill: function kill (signal) { - let proc = processes.get(this); - proc.kill(signal); - this.killed = true; - this.exitCode = null; - this.signalCode = signal; - emit(this, 'exit', this.exitCode, this.signalCode); - emit(this, 'close', this.exitCode, this.signalCode); - }, - get pid() { return processes.get(this, {}).pid || -1; } -}); - -function spawn (file, ...args) { - let cmdArgs = []; - // Default options - let options = { - cwd: null, - env: null, - encoding: 'UTF-8' - }; - - if (args[1]) { - merge(options, args[1]); - cmdArgs = args[0]; - } - else { - if (isArray(args[0])) - cmdArgs = args[0]; - else - merge(options, args[0]); - } - - if ('gid' in options) - console.warn('`gid` option is not yet supported for `child_process`'); - if ('uid' in options) - console.warn('`uid` option is not yet supported for `child_process`'); - if ('detached' in options) - console.warn('`detached` option is not yet supported for `child_process`'); - - options.file = file; - options.cmdArgs = cmdArgs; - - return Child(options); -} - -exports.spawn = spawn; - -/** - * exec(command, options, callback) - */ -function exec (cmd, ...args) { - let file, cmdArgs, callback, options = {}; - - if (isFunction(args[0])) - callback = args[0]; - else { - merge(options, args[0]); - callback = args[1]; - } - - if (isWindows) { - file = 'C:\\Windows\\System32\\cmd.exe'; - cmdArgs = ['/S/C', cmd || '']; - } - else { - file = '/bin/sh'; - cmdArgs = ['-c', cmd]; - } - - // Undocumented option from node being able to specify shell - if (options && options.shell) - file = options.shell; - - return execFile(file, cmdArgs, options, callback); -} -exports.exec = exec; -/** - * execFile (file, args, options, callback) - */ -function execFile (file, ...args) { - let cmdArgs = [], callback; - // Default options - let options = { - cwd: null, - env: null, - encoding: 'utf8', - timeout: 0, - maxBuffer: 204800, //200 KB (200*1024 bytes) - killSignal: 'SIGTERM' - }; - - if (isFunction(args[args.length - 1])) - callback = args[args.length - 1]; - - if (isArray(args[0])) { - cmdArgs = args[0]; - merge(options, args[1]); - } else if (!isFunction(args[0])) - merge(options, args[0]); - - let child = spawn(file, cmdArgs, options); - let exited = false; - let stdout = ''; - let stderr = ''; - let error = null; - let timeoutId = null; - - child.stdout.setEncoding(options.encoding); - child.stderr.setEncoding(options.encoding); - - on(child.stdout, 'data', pumpStdout); - on(child.stderr, 'data', pumpStderr); - on(child, 'close', exitHandler); - on(child, 'error', errorHandler); - - if (options.timeout > 0) { - setTimeout(() => { - kill(); - timeoutId = null; - }, options.timeout); - } - - function exitHandler (code, signal) { - - // Return if exitHandler called previously, occurs - // when multiple maxBuffer errors thrown and attempt to kill multiple - // times - if (exited) return; - exited = true; - - if (!isFunction(callback)) return; - - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - - if (!error && (code !== 0 || signal !== null)) - error = createProcessError(new Error('Command failed: ' + stderr), { - code: code, - signal: signal, - killed: !!child.killed - }); - - callback(error, stdout, stderr); - - off(child.stdout, 'data', pumpStdout); - off(child.stderr, 'data', pumpStderr); - off(child, 'close', exitHandler); - off(child, 'error', errorHandler); - } - - function errorHandler (e) { - error = e; - exitHandler(); - } - - function kill () { - try { - child.kill(options.killSignal); - } catch (e) { - // In the scenario where the kill signal happens when - // the process is already closing, just abort the kill fail - if (/library is not open/.test(e)) - return; - error = e; - exitHandler(-1, options.killSignal); - } - } - - function pumpStdout (data) { - stdout += data; - if (stdout.length > options.maxBuffer) { - error = new Error('stdout maxBuffer exceeded'); - kill(); - } - } - - function pumpStderr (data) { - stderr += data; - if (stderr.length > options.maxBuffer) { - error = new Error('stderr maxBuffer exceeded'); - kill(); - } - } - - return child; -} -exports.execFile = execFile; - -exports.fork = function fork () { - throw new Error("child_process#fork is not currently supported"); -}; - -function serializeEnv (obj) { - return Object.keys(obj || {}).map(prop => prop + '=' + obj[prop]); -} - -function createProcessError (err, options = {}) { - // If code and signal look OK, this was probably a failure - // attempting to spawn the process (like ENOENT in node) -- use - // the code from the error message - if (!options.code && !options.signal) { - let match = err.message.match(/(NS_ERROR_\w*)/); - if (match && match.length > 1) - err.code = match[1]; - else { - // If no good error message found, use the passed in exit code; - // this occurs when killing a process that's already closing, - // where we want both a valid exit code (0) and the error - err.code = options.code != null ? options.code : null; - } - } - else - err.code = options.code != null ? options.code : null; - err.signal = options.signal || null; - err.killed = options.killed || false; - return err; -} diff --git a/addon-sdk/source/lib/sdk/system/child_process/subprocess.js b/addon-sdk/source/lib/sdk/system/child_process/subprocess.js deleted file mode 100644 index e3454e95b..000000000 --- a/addon-sdk/source/lib/sdk/system/child_process/subprocess.js +++ /dev/null @@ -1,186 +0,0 @@ -/* 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, Cu } = require("chrome"); - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Subprocess.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); - -const Runtime = require("sdk/system/runtime"); -const Environment = require("sdk/system/environment").env; -const DEFAULT_ENVIRONMENT = []; -if (Runtime.OS == "Linux" && "DISPLAY" in Environment) { - DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY); -} - -function awaitPromise(promise) { - let value; - let resolved = null; - promise.then(val => { - resolved = true; - value = val; - }, val => { - resolved = false; - value = val; - }); - - while (resolved === null) - Services.tm.mainThread.processNextEvent(true); - - if (resolved === true) - return value; - throw value; -} - -let readAllData = Task.async(function* (pipe, read, callback) { - let string; - while (string = yield read(pipe)) - callback(string); -}); - -let write = (pipe, data) => { - let buffer = new Uint8Array(Array.from(data, c => c.charCodeAt(0))); - return pipe.write(data); -}; - -var subprocess = { - call: function(options) { - var result; - - let procPromise = Task.spawn(function*() { - let opts = {}; - - if (options.mergeStderr) { - opts.stderr = "stdout" - } else if (options.stderr) { - opts.stderr = "pipe"; - } - - if (options.command instanceof Ci.nsIFile) { - opts.command = options.command.path; - } else { - opts.command = yield Subprocess.pathSearch(options.command); - } - - if (options.workdir) { - opts.workdir = options.workdir; - } - - opts.arguments = options.arguments || []; - - - // Set up environment - - let envVars = options.environment || DEFAULT_ENVIRONMENT; - if (envVars.length) { - let environment = {}; - for (let val of envVars) { - let idx = val.indexOf("="); - if (idx >= 0) - environment[val.slice(0, idx)] = val.slice(idx + 1); - } - - opts.environment = environment; - } - - - let proc = yield Subprocess.call(opts); - - Object.defineProperty(result, "pid", { - value: proc.pid, - enumerable: true, - configurable: true, - }); - - - let promises = []; - - // Set up IO handlers. - - let read = pipe => pipe.readString(); - if (options.charset === null) { - read = pipe => { - return pipe.read().then(buffer => { - return String.fromCharCode(...buffer); - }); - }; - } - - if (options.stdout) - promises.push(readAllData(proc.stdout, read, options.stdout)); - - if (options.stderr && proc.stderr) - promises.push(readAllData(proc.stderr, read, options.stderr)); - - // Process stdin - - if (typeof options.stdin === "string") { - write(proc.stdin, options.stdin); - proc.stdin.close(); - } - - - // Handle process completion - - if (options.done) - Promise.all(promises) - .then(() => proc.wait()) - .then(options.done); - - return proc; - }); - - procPromise.catch(e => { - if (options.done) - options.done({exitCode: -1}, e); - else - Cu.reportError(e instanceof Error ? e : e.message || e); - }); - - if (typeof options.stdin === "function") { - // Unfortunately, some callers (child_process.js) depend on this - // being called synchronously. - options.stdin({ - write(val) { - procPromise.then(proc => { - write(proc.stdin, val); - }); - }, - - close() { - procPromise.then(proc => { - proc.stdin.close(); - }); - }, - }); - } - - result = { - get pid() { - return awaitPromise(procPromise.then(proc => { - return proc.pid; - })); - }, - - wait() { - return awaitPromise(procPromise.then(proc => { - return proc.wait().then(({exitCode}) => exitCode); - })); - }, - - kill(hard = false) { - procPromise.then(proc => { - proc.kill(hard ? 0 : undefined); - }); - }, - }; - - return result; - }, -}; - -module.exports = subprocess; diff --git a/addon-sdk/source/lib/sdk/system/environment.js b/addon-sdk/source/lib/sdk/system/environment.js deleted file mode 100644 index 13621a696..000000000 --- a/addon-sdk/source/lib/sdk/system/environment.js +++ /dev/null @@ -1,33 +0,0 @@ -/* 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": "stable" -}; - -const { Cc, Ci } = require('chrome'); -const { get, set, exists } = Cc['@mozilla.org/process/environment;1']. - getService(Ci.nsIEnvironment); - -exports.env = new Proxy({}, { - deleteProperty(target, property) { - set(property, null); - return true; - }, - - get(target, property, receiver) { - return get(property) || undefined; - }, - - has(target, property) { - return exists(property); - }, - - set(target, property, value, receiver) { - set(property, value); - return true; - } -}); diff --git a/addon-sdk/source/lib/sdk/system/events-shimmed.js b/addon-sdk/source/lib/sdk/system/events-shimmed.js deleted file mode 100644 index 14496f1f0..000000000 --- a/addon-sdk/source/lib/sdk/system/events-shimmed.js +++ /dev/null @@ -1,16 +0,0 @@ -/* 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 events = require('./events.js'); - -exports.emit = (type, event) => events.emit(type, event, true); -exports.on = (type, listener, strong) => events.on(type, listener, strong, true); -exports.once = (type, listener) => events.once(type, listener, true); -exports.off = (type, listener) => events.off(type, listener, true); diff --git a/addon-sdk/source/lib/sdk/system/events.js b/addon-sdk/source/lib/sdk/system/events.js deleted file mode 100644 index 0cf525aa1..000000000 --- a/addon-sdk/source/lib/sdk/system/events.js +++ /dev/null @@ -1,181 +0,0 @@ -/* 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 { Cc, Ci, Cu } = require('chrome'); -const { Unknown } = require('../platform/xpcom'); -const { Class } = require('../core/heritage'); -const { ns } = require('../core/namespace'); -const observerService = - Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); -const { addObserver, removeObserver, notifyObservers } = observerService; -const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); -const addObserverNoShim = ShimWaiver.getProperty(observerService, "addObserver"); -const removeObserverNoShim = ShimWaiver.getProperty(observerService, "removeObserver"); -const notifyObserversNoShim = ShimWaiver.getProperty(observerService, "notifyObservers"); -const unloadSubject = require('@loader/unload'); - -const Subject = Class({ - extends: Unknown, - initialize: function initialize(object) { - // Double-wrap the object and set a property identifying the - // wrappedJSObject as one of our wrappers to distinguish between - // subjects that are one of our wrappers (which we should unwrap - // when notifying our observers) and those that are real JS XPCOM - // components (which we should pass through unaltered). - this.wrappedJSObject = { - observersModuleSubjectWrapper: true, - object: object - }; - }, - getScriptableHelper: function() {}, - getInterfaces: function() {} -}); - -function emit(type, event, shimmed = false) { - // From bug 910599 - // We must test to see if 'subject' or 'data' is a defined property - // of the event object, but also allow primitives to be passed in, - // which the `in` operator breaks, yet `null` is an object, hence - // the long conditional - let subject = event && typeof event === 'object' && 'subject' in event ? - Subject(event.subject) : - null; - let data = event && typeof event === 'object' ? - // An object either returns its `data` property or null - ('data' in event ? event.data : null) : - // All other types return themselves (and cast to strings/null - // via observer service) - event; - if (shimmed) { - notifyObservers(subject, type, data); - } else { - notifyObserversNoShim(subject, type, data); - } -} -exports.emit = emit; - -const Observer = Class({ - extends: Unknown, - initialize: function initialize(listener) { - this.listener = listener; - }, - interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ], - observe: function(subject, topic, data) { - // Extract the wrapped object for subjects that are one of our - // wrappers around a JS object. This way we support both wrapped - // subjects created using this module and those that are real - // XPCOM components. - if (subject && typeof(subject) == 'object' && - ('wrappedJSObject' in subject) && - ('observersModuleSubjectWrapper' in subject.wrappedJSObject)) - subject = subject.wrappedJSObject.object; - - try { - this.listener({ - type: topic, - subject: subject, - data: data - }); - } - catch (error) { - console.exception(error); - } - } -}); - -const subscribers = ns(); - -function on(type, listener, strong, shimmed = false) { - // Unless last optional argument is `true` we use a weak reference to a - // listener. - let weak = !strong; - // Take list of observers associated with given `listener` function. - let observers = subscribers(listener); - // If `observer` for the given `type` is not registered yet, then - // associate an `observer` and register it. - if (!(type in observers)) { - let observer = Observer(listener); - observers[type] = observer; - if (shimmed) { - addObserver(observer, type, weak); - } else { - addObserverNoShim(observer, type, weak); - } - // WeakRef gymnastics to remove all alive observers on unload - let ref = Cu.getWeakReference(observer); - weakRefs.set(observer, ref); - stillAlive.set(ref, type); - wasShimmed.set(ref, shimmed); - } -} -exports.on = on; - -function once(type, listener, shimmed = false) { - // Note: this code assumes order in which listeners are called, which is fine - // as long as dispatch happens in same order as listener registration which - // is the case now. That being said we should be aware that this may break - // in a future if order will change. - on(type, listener, shimmed); - on(type, function cleanup() { - off(type, listener, shimmed); - off(type, cleanup, shimmed); - }, true, shimmed); -} -exports.once = once; - -function off(type, listener, shimmed = false) { - // Take list of observers as with the given `listener`. - let observers = subscribers(listener); - // If `observer` for the given `type` is registered, then - // remove it & unregister. - if (type in observers) { - let observer = observers[type]; - delete observers[type]; - if (shimmed) { - removeObserver(observer, type); - } else { - removeObserverNoShim(observer, type); - } - stillAlive.delete(weakRefs.get(observer)); - wasShimmed.delete(weakRefs.get(observer)); - } -} -exports.off = off; - -// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115 -var weakRefs = new WeakMap(); - -// and we're out of beta, we're releasing on time! -var stillAlive = new Map(); - -var wasShimmed = new Map(); - -on('sdk:loader:destroy', function onunload({ subject, data: reason }) { - // using logic from ./unload, to avoid a circular module reference - if (subject.wrappedJSObject === unloadSubject) { - off('sdk:loader:destroy', onunload, false); - - // don't bother - if (reason === 'shutdown') - return; - - stillAlive.forEach( (type, ref) => { - let observer = ref.get(); - if (observer) { - if (wasShimmed.get(ref)) { - removeObserver(observer, type); - } else { - removeObserverNoShim(observer, type); - } - } - }) - } - // a strong reference -}, true, false); diff --git a/addon-sdk/source/lib/sdk/system/globals.js b/addon-sdk/source/lib/sdk/system/globals.js deleted file mode 100644 index a1a6cf9a2..000000000 --- a/addon-sdk/source/lib/sdk/system/globals.js +++ /dev/null @@ -1,46 +0,0 @@ -/* 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" -}; - -var { Cc, Ci, CC } = require('chrome'); -var { PlainTextConsole } = require('../console/plain-text'); -var { stdout } = require('../system'); -var ScriptError = CC('@mozilla.org/scripterror;1', 'nsIScriptError'); -var consoleService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService); - -// On windows dump does not writes into stdout so cfx can't read thous dumps. -// To workaround this issue we write to a special file from which cfx will -// read and print to the console. -// For more details see: bug-673383 -exports.dump = stdout.write; - -exports.console = new PlainTextConsole(); - -// Provide CommonJS `define` to allow authoring modules in a format that can be -// loaded both into jetpack and into browser via AMD loaders. -Object.defineProperty(exports, 'define', { - // `define` is provided as a lazy getter that binds below defined `define` - // function to the module scope, so that require, exports and module - // variables remain accessible. - configurable: true, - get: function() { - let sandbox = this; - return function define(factory) { - factory = Array.slice(arguments).pop(); - factory.call(sandbox, sandbox.require, sandbox.exports, sandbox.module); - } - }, - set: function(value) { - Object.defineProperty(this, 'define', { - configurable: true, - enumerable: true, - value, - }); - }, -}); diff --git a/addon-sdk/source/lib/sdk/system/process.js b/addon-sdk/source/lib/sdk/system/process.js deleted file mode 100644 index f44a36658..000000000 --- a/addon-sdk/source/lib/sdk/system/process.js +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 { - exit, version, stdout, stderr, platform, architecture -} = require("../system"); - -/** - * Supported - */ - -exports.stdout = stdout; -exports.stderr = stderr; -exports.version = version; -exports.versions = {}; -exports.config = {}; -exports.arch = architecture; -exports.platform = platform; -exports.exit = exit; - -/** - * Partial support - */ - -// An alias to `setTimeout(fn, 0)`, which isn't the same as node's `nextTick`, -// but atleast ensures it'll occur asynchronously -exports.nextTick = (callback) => setTimeout(callback, 0); - -/** - * Unsupported - */ - -exports.maxTickDepth = 1000; -exports.pid = 0; -exports.title = ""; -exports.stdin = {}; -exports.argv = []; -exports.execPath = ""; -exports.execArgv = []; -exports.abort = function () {}; -exports.chdir = function () {}; -exports.cwd = function () {}; -exports.env = {}; -exports.getgid = function () {}; -exports.setgid = function () {}; -exports.getuid = function () {}; -exports.setuid = function () {}; -exports.getgroups = function () {}; -exports.setgroups = function () {}; -exports.initgroups = function () {}; -exports.kill = function () {}; -exports.memoryUsage = function () {}; -exports.umask = function () {}; -exports.uptime = function () {}; -exports.hrtime = function () {}; diff --git a/addon-sdk/source/lib/sdk/system/runtime.js b/addon-sdk/source/lib/sdk/system/runtime.js deleted file mode 100644 index 9a70f142d..000000000 --- a/addon-sdk/source/lib/sdk/system/runtime.js +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 { Cc, Ci } = require("chrome"); -const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); - -exports.inSafeMode = runtime.inSafeMode; -exports.OS = runtime.OS; -exports.processType = runtime.processType; -exports.widgetToolkit = runtime.widgetToolkit; -exports.processID = runtime.processID; - -// Attempt to access `XPCOMABI` may throw exception, in which case exported -// `XPCOMABI` will be set to `null`. -// https://mxr.mozilla.org/mozilla-central/source/toolkit/xre/nsAppRunner.cpp#732 -try { - exports.XPCOMABI = runtime.XPCOMABI; -} -catch (error) { - exports.XPCOMABI = null; -} diff --git a/addon-sdk/source/lib/sdk/system/unload.js b/addon-sdk/source/lib/sdk/system/unload.js deleted file mode 100644 index 98ab5f8f3..000000000 --- a/addon-sdk/source/lib/sdk/system/unload.js +++ /dev/null @@ -1,104 +0,0 @@ -/* 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/. */ - -// Parts of this module were taken from narwhal: -// -// http://narwhaljs.org - -module.metadata = { - "stability": "experimental" -}; - -const { Cu } = require('chrome'); -const { on, off } = require('./events'); -const unloadSubject = require('@loader/unload'); - -const observers = []; -const unloaders = []; - -function WeakObserver(inner) { - this._inner = Cu.getWeakReference(inner); -} - -Object.defineProperty(WeakObserver.prototype, 'value', { - get: function() { this._inner.get() } -}); - -var when = exports.when = function when(observer, opts) { - opts = opts || {}; - for (var i = 0; i < observers.length; ++i) { - if (observers[i] === observer || observers[i].value === observer) { - return; - } - } - if (opts.weak) { - observers.unshift(new WeakObserver(observer)); - } else { - observers.unshift(observer); - } -}; - -var ensure = exports.ensure = function ensure(obj, destructorName) { - if (!destructorName) - destructorName = "unload"; - if (!(destructorName in obj)) - throw new Error("object has no '" + destructorName + "' property"); - - let called = false; - let originalDestructor = obj[destructorName]; - - function unloadWrapper(reason) { - if (!called) { - called = true; - let index = unloaders.indexOf(unloadWrapper); - if (index == -1) - throw new Error("internal error: unloader not found"); - unloaders.splice(index, 1); - originalDestructor.call(obj, reason); - originalDestructor = null; - destructorName = null; - obj = null; - } - }; - - // TODO: Find out why the order is inverted here. It seems that - // it may be causing issues! - unloaders.push(unloadWrapper); - - obj[destructorName] = unloadWrapper; -}; - -function unload(reason) { - observers.forEach(function(observer) { - try { - if (observer instanceof WeakObserver) { - observer = observer.value; - } - if (typeof observer === 'function') { - observer(reason); - } - } - catch (error) { - console.exception(error); - } - }); -} - -when(function(reason) { - unloaders.slice().forEach(function(unloadWrapper) { - unloadWrapper(reason); - }); -}); - -on('sdk:loader:destroy', function onunload({ subject, data: reason }) { - // If this loader is unload then `subject.wrappedJSObject` will be - // `destructor`. - if (subject.wrappedJSObject === unloadSubject) { - off('sdk:loader:destroy', onunload); - unload(reason); - } -// Note that we use strong reference to listener here to make sure it's not -// GC-ed, which may happen otherwise since nothing keeps reference to `onunolad` -// function. -}, true); diff --git a/addon-sdk/source/lib/sdk/system/xul-app.js b/addon-sdk/source/lib/sdk/system/xul-app.js deleted file mode 100644 index 612386f77..000000000 --- a/addon-sdk/source/lib/sdk/system/xul-app.js +++ /dev/null @@ -1,12 +0,0 @@ -/* 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": "experimental" -}; - -const { XulApp } = require("./xul-app.jsm"); - -Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]); diff --git a/addon-sdk/source/lib/sdk/system/xul-app.jsm b/addon-sdk/source/lib/sdk/system/xul-app.jsm deleted file mode 100644 index 90681bb1b..000000000 --- a/addon-sdk/source/lib/sdk/system/xul-app.jsm +++ /dev/null @@ -1,242 +0,0 @@ -/* 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"; - -this.EXPORTED_SYMBOLS = [ "XulApp" ]; - -var { classes: Cc, interfaces: Ci } = Components; - -var exports = {}; -this.XulApp = exports; - -var appInfo; - -// NOTE: below is required to avoid failing xpcshell tests, -// which do not implement nsIXULAppInfo -// See Bug 1114752 https://bugzilla.mozilla.org/show_bug.cgi?id=1114752 -try { - appInfo = Cc["@mozilla.org/xre/app-info;1"] - .getService(Ci.nsIXULAppInfo); -} -catch (e) { - // xpcshell test case - appInfo = {}; -} -var vc = Cc["@mozilla.org/xpcom/version-comparator;1"] - .getService(Ci.nsIVersionComparator); - -var ID = exports.ID = appInfo.ID; -var name = exports.name = appInfo.name; -var version = exports.version = appInfo.version; -var platformVersion = exports.platformVersion = appInfo.platformVersion; - -// The following mapping of application names to GUIDs was taken from: -// -// https://addons.mozilla.org/en-US/firefox/pages/appversions -// -// Using the GUID instead of the app's name is preferable because sometimes -// re-branded versions of a product have different names: for instance, -// Firefox, Minefield, Iceweasel, and Shiretoko all have the same -// GUID. - -var ids = exports.ids = { - Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", - Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}", - SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", - Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}", - Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}", - Instantbird: "{33cb9019-c295-46dd-be21-8c4936574bee}" -}; - -function is(name) { - if (!(name in ids)) - throw new Error("Unkown Mozilla Application: " + name); - return ID == ids[name]; -}; -exports.is = is; - -function isOneOf(names) { - for (var i = 0; i < names.length; i++) - if (is(names[i])) - return true; - return false; -}; -exports.isOneOf = isOneOf; - -/** - * Use this to check whether the given version (e.g. xulApp.platformVersion) - * is in the given range. Versions must be in version comparator-compatible - * format. See MDC for details: - * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator - */ -var versionInRange = exports.versionInRange = -function versionInRange(version, lowInclusive, highExclusive) { - return (vc.compare(version, lowInclusive) >= 0) && - (vc.compare(version, highExclusive) < 0); -} - -const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/; -const reOnlyInifinity = /^[<>]?=?\s*[*x]$/; -const reSubInfinity = /\.[*x]/g; -const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/; -const reRangeSeparator = /\s*\|\|\s*/; - -const compares = { - "=": function (c) { return c === 0 }, - ">=": function (c) { return c >= 0 }, - "<=": function (c) { return c <= 0}, - "<": function (c) { return c < 0 }, - ">": function (c) { return c > 0 } -} - -function normalizeRange(range) { - return range - .replace(reOnlyInifinity, "") - .replace(reSubInfinity, ".*") - .replace(reHyphenRange, ">=$1 <=$2") -} - -/** - * Compare the versions given, using the comparison operator provided. - * Internal use only. - * - * @example - * compareVersion("1.2", "<=", "1.*") // true - * - * @param {String} version - * A version to compare - * - * @param {String} comparison - * The comparison operator - * - * @param {String} compareVersion - * A version to compare - */ -function compareVersion(version, comparison, compareVersion) { - let hasWildcard = compareVersion.indexOf("*") !== -1; - - comparison = comparison || "="; - - if (hasWildcard) { - switch (comparison) { - case "=": - let zeroVersion = compareVersion.replace(reSubInfinity, ".0"); - return versionInRange(version, zeroVersion, compareVersion); - case ">=": - compareVersion = compareVersion.replace(reSubInfinity, ".0"); - break; - } - } - - let compare = compares[comparison]; - - return typeof compare === "function" && compare(vc.compare(version, compareVersion)); -} - -/** - * Returns `true` if `version` satisfies the `versionRange` given. - * If only an argument is passed, is used as `versionRange` and compared against - * `xulApp.platformVersion`. - * - * `versionRange` is either a string which has one or more space-separated - * descriptors, or a range like "fromVersion - toVersion". - * Version range descriptors may be any of the following styles: - * - * - "version" Must match `version` exactly - * - "=version" Same as just `version` - * - ">version" Must be greater than `version` - * - ">=version" Must be greater or equal than `version` - * - "<version" Must be less than `version` - * - "<=version" Must be less or equal than `version` - * - "1.2.x" or "1.2.*" See 'X version ranges' below - * - "*" or "" (just an empty string) Matches any version - * - "version1 - version2" Same as ">=version1 <=version2" - * - "range1 || range2" Passes if either `range1` or `range2` are satisfied - * - * For example, these are all valid: - * - "1.0.0 - 2.9999.9999" - * - ">=1.0.2 <2.1.2" - * - ">1.0.2 <=2.3.4" - * - "2.0.1" - * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0" - * - "2.x" (equivalent to "2.*") - * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0") - */ -function satisfiesVersion(version, versionRange) { - if (arguments.length === 1) { - versionRange = version; - version = appInfo.version; - } - - let ranges = versionRange.trim().split(reRangeSeparator); - - return ranges.some(function(range) { - range = normalizeRange(range); - - // No versions' range specified means that any version satisfies the - // requirements. - if (range === "") - return true; - - let matches = range.match(reVersionRange); - - if (!matches) - return false; - - let [, lowMod, lowVer, highMod, highVer] = matches; - - return compareVersion(version, lowMod, lowVer) && (highVer !== undefined - ? compareVersion(version, highMod, highVer) - : true); - }); -} -exports.satisfiesVersion = satisfiesVersion; - -/** - * Ensure the current application satisfied the requirements specified in the - * module given. If not, an exception related to the incompatibility is - * returned; `null` otherwise. - * - * @param {Object} module - * The module to check - * @returns {Error} - */ -function incompatibility(module) { - let { metadata, id } = module; - - // if metadata or engines are not specified we assume compatibility is not - // an issue. - if (!metadata || !("engines" in metadata)) - return null; - - let { engines } = metadata; - - if (engines === null || typeof(engines) !== "object") - return new Error("Malformed engines' property in metadata"); - - let applications = Object.keys(engines); - - let versionRange; - applications.forEach(function(name) { - if (is(name)) { - versionRange = engines[name]; - // Continue iteration. We want to ensure the module doesn't - // contain a typo in the applications' name or some unknown - // application - `is` function throws an exception in that case. - } - }); - - if (typeof(versionRange) === "string") { - if (satisfiesVersion(versionRange)) - return null; - - return new Error("Unsupported Application version: The module " + id + - " currently supports only version " + versionRange + " of " + - name + "."); - } - - return new Error("Unsupported Application: The module " + id + - " currently supports only " + applications.join(", ") + ".") -} -exports.incompatibility = incompatibility; diff --git a/addon-sdk/source/lib/sdk/tab/events.js b/addon-sdk/source/lib/sdk/tab/events.js deleted file mode 100644 index e431cc9d2..000000000 --- a/addon-sdk/source/lib/sdk/tab/events.js +++ /dev/null @@ -1,74 +0,0 @@ -/* 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"; - -// This module provides temporary shim until Bug 843901 is shipped. -// It basically registers tab event listeners on all windows that get -// opened and forwards them through observer notifications. - -module.metadata = { - "stability": "experimental" -}; - -const { Ci } = require("chrome"); -const { windows, isInteractive } = require("../window/utils"); -const { events } = require("../browser/events"); -const { open } = require("../event/dom"); -const { filter, map, merge, expand } = require("../event/utils"); -const isFennec = require("sdk/system/xul-app").is("Fennec"); - -// Module provides event stream (in nodejs style) that emits data events -// for all the tab events that happen in running firefox. At the moment -// it does it by registering listeners on all browser windows and then -// forwarding events when they occur to a stream. This will become obsolete -// once Bug 843901 is fixed, and we'll just leverage observer notifications. - -// Set of tab events that this module going to aggregate and expose. -const TYPES = ["TabOpen","TabClose","TabSelect","TabMove","TabPinned", - "TabUnpinned"]; - -// Utility function that given a browser `window` returns stream of above -// defined tab events for all tabs on the given window. -function tabEventsFor(window) { - // Map supported event types to a streams of those events on the given - // `window` and than merge these streams into single form stream off - // all events. - let channels = TYPES.map(type => open(window, type)); - return merge(channels); -} - -// Create our event channels. We do this in a separate function to -// minimize the chance of leaking intermediate objects on the global. -function makeEvents() { - // Filter DOMContentLoaded events from all the browser events. - var readyEvents = filter(events, e => e.type === "DOMContentLoaded"); - // Map DOMContentLoaded events to it's target browser windows. - var futureWindows = map(readyEvents, e => e.target); - // Expand all browsers that will become interactive to supported tab events - // on these windows. Result will be a tab events from all tabs of all windows - // that will become interactive. - var eventsFromFuture = expand(futureWindows, tabEventsFor); - - // Above covers only windows that will become interactive in a future, but some - // windows may already be interactive so we pick those and expand to supported - // tab events for them too. - var interactiveWindows = windows("navigator:browser", { includePrivate: true }). - filter(isInteractive); - var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor)); - - - // Finally merge stream of tab events from future windows and current windows - // to cover all tab events on all windows that will open. - return merge([eventsFromInteractive, eventsFromFuture]); -} - -// Map events to Fennec format if necessary -exports.events = map(makeEvents(), function (event) { - return !isFennec ? event : { - type: event.type, - target: event.target.ownerDocument.defaultView.BrowserApp - .getTabForBrowser(event.target) - }; -}); diff --git a/addon-sdk/source/lib/sdk/tabs.js b/addon-sdk/source/lib/sdk/tabs.js deleted file mode 100644 index f61cad478..000000000 --- a/addon-sdk/source/lib/sdk/tabs.js +++ /dev/null @@ -1,17 +0,0 @@ -/* 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" -}; - -if (require("./system/xul-app").is("Fennec")) { - module.exports = require("./windows/tabs-fennec").tabs; -} -else { - module.exports = require("./tabs/tabs-firefox"); -} - -const tabs = module.exports; diff --git a/addon-sdk/source/lib/sdk/tabs/common.js b/addon-sdk/source/lib/sdk/tabs/common.js deleted file mode 100644 index 9ee512a7b..000000000 --- a/addon-sdk/source/lib/sdk/tabs/common.js +++ /dev/null @@ -1,34 +0,0 @@ -/* 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; diff --git a/addon-sdk/source/lib/sdk/tabs/events.js b/addon-sdk/source/lib/sdk/tabs/events.js deleted file mode 100644 index 65650f9dc..000000000 --- a/addon-sdk/source/lib/sdk/tabs/events.js +++ /dev/null @@ -1,39 +0,0 @@ -/* 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); -} diff --git a/addon-sdk/source/lib/sdk/tabs/helpers.js b/addon-sdk/source/lib/sdk/tabs/helpers.js deleted file mode 100644 index b2c8aa013..000000000 --- a/addon-sdk/source/lib/sdk/tabs/helpers.js +++ /dev/null @@ -1,22 +0,0 @@ -/* 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; diff --git a/addon-sdk/source/lib/sdk/tabs/namespace.js b/addon-sdk/source/lib/sdk/tabs/namespace.js deleted file mode 100644 index 3553b1a99..000000000 --- a/addon-sdk/source/lib/sdk/tabs/namespace.js +++ /dev/null @@ -1,10 +0,0 @@ -/* 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(); diff --git a/addon-sdk/source/lib/sdk/tabs/observer.js b/addon-sdk/source/lib/sdk/tabs/observer.js deleted file mode 100644 index 4e935cd62..000000000 --- a/addon-sdk/source/lib/sdk/tabs/observer.js +++ /dev/null @@ -1,113 +0,0 @@ -/* 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(); diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js deleted file mode 100644 index 3927337f6..000000000 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ /dev/null @@ -1,249 +0,0 @@ -/* 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; -}); diff --git a/addon-sdk/source/lib/sdk/tabs/tab-firefox.js b/addon-sdk/source/lib/sdk/tabs/tab-firefox.js deleted file mode 100644 index f1da92379..000000000 --- a/addon-sdk/source/lib/sdk/tabs/tab-firefox.js +++ /dev/null @@ -1,353 +0,0 @@ -/* 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); -}); diff --git a/addon-sdk/source/lib/sdk/tabs/tab.js b/addon-sdk/source/lib/sdk/tabs/tab.js deleted file mode 100644 index fa2272494..000000000 --- a/addon-sdk/source/lib/sdk/tabs/tab.js +++ /dev/null @@ -1,24 +0,0 @@ -/* 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))); diff --git a/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js b/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js deleted file mode 100644 index 1eefecb4c..000000000 --- a/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js +++ /dev/null @@ -1,135 +0,0 @@ -/* 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); - } -}); diff --git a/addon-sdk/source/lib/sdk/tabs/utils.js b/addon-sdk/source/lib/sdk/tabs/utils.js deleted file mode 100644 index eae3d41fe..000000000 --- a/addon-sdk/source/lib/sdk/tabs/utils.js +++ /dev/null @@ -1,370 +0,0 @@ -/* 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; diff --git a/addon-sdk/source/lib/sdk/tabs/worker.js b/addon-sdk/source/lib/sdk/tabs/worker.js deleted file mode 100644 index d2ba33696..000000000 --- a/addon-sdk/source/lib/sdk/tabs/worker.js +++ /dev/null @@ -1,17 +0,0 @@ -/* 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;
\ No newline at end of file diff --git a/addon-sdk/source/lib/sdk/test.js b/addon-sdk/source/lib/sdk/test.js deleted file mode 100644 index e7e3df840..000000000 --- a/addon-sdk/source/lib/sdk/test.js +++ /dev/null @@ -1,114 +0,0 @@ -/* 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 { Cu } = require("chrome"); -const { Task } = require("resource://gre/modules/Task.jsm", {}); -const { defer } = require("sdk/core/promise"); -const BaseAssert = require("sdk/test/assert").Assert; -const { isFunction, isObject, isGenerator } = require("sdk/lang/type"); -const { extend } = require("sdk/util/object"); - -exports.Assert = BaseAssert; - -/** - * Function takes test `suite` object in CommonJS format and defines all of the - * tests from that suite and nested suites in a jetpack format on a given - * `target` object. Optionally third argument `prefix` can be passed to prefix - * all the test names. - */ -function defineTestSuite(target, suite, prefix) { - prefix = prefix || ""; - // If suite defines `Assert` that's what `assert` object have to be created - // from and passed to a test function (This allows custom assertion functions) - // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1 - let Assert = suite.Assert || BaseAssert; - // Going through each item in the test suite and wrapping it into a - // Jetpack test format. - Object.keys(suite).forEach(function(key) { - // If name starts with test then it's a test function or suite. - if (key.indexOf("test") === 0) { - let test = suite[key]; - - // For each test function so we create a wrapper test function in a - // jetpack format and copy that to a `target` exports. - if (isFunction(test)) { - - // Since names of the test may match across suites we use full object - // path as a name to avoid overriding same function. - target[prefix + key] = function(options) { - - // Creating `assert` functions for this test. - let assert = Assert(options); - assert.end = () => options.done(); - - // If test function is a generator use a task JS to allow yield-ing - // style test runs. - if (isGenerator(test)) { - options.waitUntilDone(); - Task.spawn(test.bind(null, assert)). - catch(assert.fail). - then(assert.end); - } - - // If CommonJS test function expects more than one argument - // it means that test is async and second argument is a callback - // to notify that test is finished. - else if (1 < test.length) { - // Letting test runner know that test is executed async and - // creating a callback function that CommonJS tests will call - // once it's done. - options.waitUntilDone(); - test(assert, function() { - options.done(); - }); - } - - // Otherwise CommonJS test is synchronous so we call it only with - // one argument. - else { - test(assert); - } - } - } - - // If it's an object then it's a test suite containing test function - // and / or nested test suites. In that case we just extend prefix used - // and call this function to copy and wrap tests from nested suite. - else if (isObject(test)) { - // We need to clone `tests` instead of modifying it, since it's very - // likely that it is frozen (usually test suites imported modules). - test = extend(Object.prototype, test, { - Assert: test.Assert || Assert - }); - defineTestSuite(target, test, prefix + key + "."); - } - } - }); -} - -/** - * This function is a CommonJS test runner function, but since Jetpack test - * runner and test format is different from CommonJS this function shims given - * `exports` with all its tests into a Jetpack test format so that the built-in - * test runner will be able to run CommonJS test without manual changes. - */ -exports.run = function run(exports) { - // We can't leave old properties on exports since those are test in a CommonJS - // format that why we move everything to a new `suite` object. - let suite = {}; - Object.keys(exports).forEach(function(key) { - suite[key] = exports[key]; - delete exports[key]; - }); - - // Now we wrap all the CommonJS tests to a Jetpack format and define - // those to a given `exports` object since that where jetpack test runner - // will look for them. - defineTestSuite(exports, suite); -}; diff --git a/addon-sdk/source/lib/sdk/test/assert.js b/addon-sdk/source/lib/sdk/test/assert.js deleted file mode 100644 index 8478c8414..000000000 --- a/addon-sdk/source/lib/sdk/test/assert.js +++ /dev/null @@ -1,366 +0,0 @@ -/* 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 { isFunction, isNull, isObject, isString, - isRegExp, isArray, isDate, isPrimitive, - isUndefined, instanceOf, source } = require("../lang/type"); - -/** - * The `AssertionError` is defined in assert. - * @extends Error - * @example - * new assert.AssertionError({ - * message: message, - * actual: actual, - * expected: expected - * }) - */ -function AssertionError(options) { - let assertionError = Object.create(AssertionError.prototype); - - if (isString(options)) - options = { message: options }; - if ("actual" in options) - assertionError.actual = options.actual; - if ("expected" in options) - assertionError.expected = options.expected; - if ("operator" in options) - assertionError.operator = options.operator; - - assertionError.message = options.message; - assertionError.stack = new Error().stack; - return assertionError; -} -AssertionError.prototype = Object.create(Error.prototype, { - constructor: { value: AssertionError }, - name: { value: "AssertionError", enumerable: true }, - toString: { value: function toString() { - let value; - if (this.message) { - value = this.name + " : " + this.message; - } - else { - value = [ - this.name + " : ", - source(this.expected), - this.operator, - source(this.actual) - ].join(" "); - } - return value; - }} -}); -exports.AssertionError = AssertionError; - -function Assert(logger) { - let assert = Object.create(Assert.prototype, { _log: { value: logger }}); - - assert.fail = assert.fail.bind(assert); - assert.pass = assert.pass.bind(assert); - - return assert; -} - -Assert.prototype = { - fail: function fail(e) { - if (!e || typeof(e) !== 'object') { - this._log.fail(e); - return; - } - let message = e.message; - try { - if ('operator' in e) { - message += [ - " -", - source(e.actual), - e.operator, - source(e.expected) - ].join(" "); - } - } - catch(e) {} - this._log.fail(message); - }, - pass: function pass(message) { - this._log.pass(message); - return true; - }, - error: function error(e) { - this._log.exception(e); - }, - ok: function ok(value, message) { - if (!!!value) { - this.fail({ - actual: value, - expected: true, - message: message, - operator: "==" - }); - return false; - } - - this.pass(message); - return true; - }, - - /** - * The equality assertion tests shallow, coercive equality with `==`. - * @example - * assert.equal(1, 1, "one is one"); - */ - equal: function equal(actual, expected, message) { - if (actual == expected) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "==" - }); - return false; - }, - - /** - * The non-equality assertion tests for whether two objects are not equal - * with `!=`. - * @example - * assert.notEqual(1, 2, "one is not two"); - */ - notEqual: function notEqual(actual, expected, message) { - if (actual != expected) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "!=", - }); - return false; - }, - - /** - * The equivalence assertion tests a deep (with `===`) equality relation. - * @example - * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects") - */ - deepEqual: function deepEqual(actual, expected, message) { - if (isDeepEqual(actual, expected)) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "deepEqual" - }); - return false; - }, - - /** - * The non-equivalence assertion tests for any deep (with `===`) inequality. - * @example - * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }), - * "object's inherit from different prototypes"); - */ - notDeepEqual: function notDeepEqual(actual, expected, message) { - if (!isDeepEqual(actual, expected)) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "notDeepEqual" - }); - return false; - }, - - /** - * The strict equality assertion tests strict equality, as determined by - * `===`. - * @example - * assert.strictEqual(null, null, "`null` is `null`") - */ - strictEqual: function strictEqual(actual, expected, message) { - if (actual === expected) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "===" - }); - return false; - }, - - /** - * The strict non-equality assertion tests for strict inequality, as - * determined by `!==`. - * @example - * assert.notStrictEqual(null, undefined, "`null` is not `undefined`"); - */ - notStrictEqual: function notStrictEqual(actual, expected, message) { - if (actual !== expected) { - this.pass(message); - return true; - } - - this.fail({ - actual: actual, - expected: expected, - message: message, - operator: "!==" - }); - return false; - }, - - /** - * The assertion whether or not given `block` throws an exception. If optional - * `Error` argument is provided and it's type of function thrown error is - * asserted to be an instance of it, if type of `Error` is string then message - * of throw exception is asserted to contain it. - * @param {Function} block - * Function that is expected to throw. - * @param {Error|RegExp} [Error] - * Error constructor that is expected to be thrown or a string that - * must be contained by a message of the thrown exception, or a RegExp - * matching a message of the thrown exception. - * @param {String} message - * Description message - * - * @examples - * - * assert.throws(function block() { - * doSomething(4) - * }, "Object is expected", "Incorrect argument is passed"); - * - * assert.throws(function block() { - * Object.create(5) - * }, TypeError, "TypeError is thrown"); - */ - throws: function throws(block, Error, message) { - let threw = false; - let exception = null; - - // If third argument is not provided and second argument is a string it - // means that optional `Error` argument was not passed, so we shift - // arguments. - if (isString(Error) && isUndefined(message)) { - message = Error; - Error = undefined; - } - - // Executing given `block`. - try { - block(); - } - catch (e) { - threw = true; - exception = e; - } - - // If exception was thrown and `Error` argument was not passed assert is - // passed. - if (threw && (isUndefined(Error) || - // If passed `Error` is RegExp using it's test method to - // assert thrown exception message. - (isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) || - // If passed `Error` is a constructor function testing if - // thrown exception is an instance of it. - (isFunction(Error) && instanceOf(exception, Error)))) - { - this.pass(message); - return true; - } - - // Otherwise we report assertion failure. - let failure = { - message: message, - operator: "matches" - }; - - if (exception) { - failure.actual = exception.message || exception.toString(); - } - - if (Error) { - failure.expected = Error.toString(); - } - - this.fail(failure); - return false; - } -}; -exports.Assert = Assert; - -function isDeepEqual(actual, expected) { - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - } - - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - else if (isDate(actual) && isDate(expected)) { - return actual.getTime() === expected.getTime(); - } - - // XXX specification bug: this should be specified - else if (isPrimitive(actual) || isPrimitive(expected)) { - return expected === actual; - } - - // 7.3. Other pairs that do not both pass typeof value == "object", - // equivalence is determined by ==. - else if (!isObject(actual) && !isObject(expected)) { - return actual == expected; - } - - // 7.4. For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical "prototype" property. Note: this - // accounts for both named and indexed properties on Arrays. - else { - return actual.prototype === expected.prototype && - isEquivalent(actual, expected); - } -} - -function isEquivalent(a, b, stack) { - let aKeys = Object.keys(a); - let bKeys = Object.keys(b); - - return aKeys.length === bKeys.length && - isArrayEquivalent(aKeys.sort(), bKeys.sort()) && - aKeys.every(function(key) { - return isDeepEqual(a[key], b[key], stack) - }); -} - -function isArrayEquivalent(a, b, stack) { - return isArray(a) && isArray(b) && - a.every(function(value, index) { - return isDeepEqual(value, b[index]); - }); -} diff --git a/addon-sdk/source/lib/sdk/test/harness.js b/addon-sdk/source/lib/sdk/test/harness.js deleted file mode 100644 index 1b31a1c79..000000000 --- a/addon-sdk/source/lib/sdk/test/harness.js +++ /dev/null @@ -1,645 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { Loader } = require('./loader'); -const { serializeStack, parseStack } = require("toolkit/loader"); -const { setTimeout } = require('../timers'); -const { PlainTextConsole } = require("../console/plain-text"); -const { when: unload } = require("../system/unload"); -const { format, fromException } = require("../console/traceback"); -const system = require("../system"); -const { gc: gcPromise } = require('./memory'); -const { defer } = require('../core/promise'); -const { extend } = require('../core/heritage'); - -// Trick manifest builder to make it think we need these modules ? -const unit = require("../deprecated/unit-test"); -const test = require("../../test"); -const url = require("../url"); - -function emptyPromise() { - let { promise, resolve } = defer(); - resolve(); - return promise; -} - -var cService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService); - -// The console used to log messages -var testConsole; - -// Cuddlefish loader in which we load and execute tests. -var loader; - -// Function to call when we're done running tests. -var onDone; - -// Function to print text to a console, w/o CR at the end. -var print; - -// How many more times to run all tests. -var iterationsLeft; - -// Whether to report memory profiling information. -var profileMemory; - -// Whether we should stop as soon as a test reports a failure. -var stopOnError; - -// Function to call to retrieve a list of tests to execute -var findAndRunTests; - -// Combined information from all test runs. -var results; - -// A list of the compartments and windows loaded after startup -var startLeaks; - -// JSON serialization of last memory usage stats; we keep it stringified -// so we don't actually change the memory usage stats (in terms of objects) -// of the JSRuntime we're profiling. -var lastMemoryUsage; - -function analyzeRawProfilingData(data) { - var graph = data.graph; - var shapes = {}; - - // Convert keys in the graph from strings to ints. - // TODO: Can we get rid of this ridiculousness? - var newGraph = {}; - for (id in graph) { - newGraph[parseInt(id)] = graph[id]; - } - graph = newGraph; - - var modules = 0; - var moduleIds = []; - var moduleObjs = {UNKNOWN: 0}; - for (let name in data.namedObjects) { - moduleObjs[name] = 0; - moduleIds[data.namedObjects[name]] = name; - modules++; - } - - var count = 0; - for (id in graph) { - var parent = graph[id].parent; - while (parent) { - if (parent in moduleIds) { - var name = moduleIds[parent]; - moduleObjs[name]++; - break; - } - if (!(parent in graph)) { - moduleObjs.UNKNOWN++; - break; - } - parent = graph[parent].parent; - } - count++; - } - - print("\nobject count is " + count + " in " + modules + " modules" + - " (" + data.totalObjectCount + " across entire JS runtime)\n"); - if (lastMemoryUsage) { - var last = JSON.parse(lastMemoryUsage); - var diff = { - moduleObjs: dictDiff(last.moduleObjs, moduleObjs), - totalObjectClasses: dictDiff(last.totalObjectClasses, - data.totalObjectClasses) - }; - - for (let name in diff.moduleObjs) - print(" " + diff.moduleObjs[name] + " in " + name + "\n"); - for (let name in diff.totalObjectClasses) - print(" " + diff.totalObjectClasses[name] + " instances of " + - name + "\n"); - } - lastMemoryUsage = JSON.stringify( - {moduleObjs: moduleObjs, - totalObjectClasses: data.totalObjectClasses} - ); -} - -function dictDiff(last, curr) { - var diff = {}; - - for (let name in last) { - var result = (curr[name] || 0) - last[name]; - if (result) - diff[name] = (result > 0 ? "+" : "") + result; - } - for (let name in curr) { - var result = curr[name] - (last[name] || 0); - if (result) - diff[name] = (result > 0 ? "+" : "") + result; - } - return diff; -} - -function reportMemoryUsage() { - if (!profileMemory) { - return emptyPromise(); - } - - return gcPromise().then((() => { - var mgr = Cc["@mozilla.org/memory-reporter-manager;1"] - .getService(Ci.nsIMemoryReporterManager); - let count = 0; - function logReporter(process, path, kind, units, amount, description) { - print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n"); - } - mgr.getReportsForThisProcess(logReporter, null, /* anonymize = */ false); - })); -} - -var gWeakrefInfo; - -function checkMemory() { - return gcPromise().then(_ => { - let leaks = getPotentialLeaks(); - - let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) { - return !(url in startLeaks.compartments); - }); - - let windowURLs = Object.keys(leaks.windows).filter(function(url) { - return !(url in startLeaks.windows); - }); - - for (let url of compartmentURLs) - console.warn("LEAKED", leaks.compartments[url]); - - for (let url of windowURLs) - console.warn("LEAKED", leaks.windows[url]); - }).then(showResults); -} - -function showResults() { - let { promise, resolve } = defer(); - - if (gWeakrefInfo) { - gWeakrefInfo.forEach( - function(info) { - var ref = info.weakref.get(); - if (ref !== null) { - var data = ref.__url__ ? ref.__url__ : ref; - var warning = data == "[object Object]" - ? "[object " + data.constructor.name + "(" + - Object.keys(data).join(", ") + ")]" - : data; - console.warn("LEAK", warning, info.bin); - } - } - ); - } - - onDone(results); - - resolve(); - return promise; -} - -function cleanup() { - let coverObject = {}; - try { - loader.unload(); - - if (loader.globals.console.errorsLogged && !results.failed) { - results.failed++; - console.error("warnings and/or errors were logged."); - } - - if (consoleListener.errorsLogged && !results.failed) { - console.warn(consoleListener.errorsLogged + " " + - "warnings or errors were logged to the " + - "platform's nsIConsoleService, which could " + - "be of no consequence; however, they could also " + - "be indicative of aberrant behavior."); - } - - // read the code coverage object, if it exists, from CoverJS-moz - if (typeof loader.globals.global == "object") { - coverObject = loader.globals.global['__$coverObject'] || {}; - } - - consoleListener.errorsLogged = 0; - loader = null; - - consoleListener.unregister(); - - Cu.forceGC(); - } - catch (e) { - results.failed++; - console.error("unload.send() threw an exception."); - console.exception(e); - }; - - setTimeout(require("./options").checkMemory ? checkMemory : showResults, 1); - - // dump the coverobject - if (Object.keys(coverObject).length){ - const self = require('sdk/self'); - const {pathFor} = require("sdk/system"); - let file = require('sdk/io/file'); - const {env} = require('sdk/system/environment'); - console.log("CWD:", env.PWD); - let out = file.join(env.PWD,'coverstats-'+self.id+'.json'); - console.log('coverstats:', out); - let outfh = file.open(out,'w'); - outfh.write(JSON.stringify(coverObject,null,2)); - outfh.flush(); - outfh.close(); - } -} - -function getPotentialLeaks() { - Cu.forceGC(); - - // Things we can assume are part of the platform and so aren't leaks - let GOOD_BASE_URLS = [ - "chrome://", - "resource:///", - "resource://app/", - "resource://gre/", - "resource://gre-resources/", - "resource://pdf.js/", - "resource://pdf.js.components/", - "resource://services-common/", - "resource://services-crypto/", - "resource://services-sync/" - ]; - - let ioService = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - let uri = ioService.newURI("chrome://global/content/", "UTF-8", null); - let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. - getService(Ci.nsIChromeRegistry); - uri = chromeReg.convertChromeURL(uri); - let spec = uri.spec; - let pos = spec.indexOf("!/"); - GOOD_BASE_URLS.push(spec.substring(0, pos + 2)); - - let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)"); - let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/"); - let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$"); - let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active"); - let windowDetails = new RegExp("^(.*), id=.*$"); - - function isPossibleLeak(item) { - if (!item.location) - return false; - - for (let url of GOOD_BASE_URLS) { - if (item.location.substring(0, url.length) == url) { - return false; - } - } - - return true; - } - - let compartments = {}; - let windows = {}; - function logReporter(process, path, kind, units, amount, description) { - let matches; - - if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) { - if (matches[1] in compartments) - return; - - let details = compartmentDetails.exec(matches[1]); - if (!details) { - console.error("Unable to parse compartment detail " + matches[1]); - return; - } - - let item = { - path: matches[1], - principal: details[1], - location: details[2] ? details[2].replace(/\\/g, "/") : undefined, - source: details[3] ? details[3].split(" -> ").reverse() : undefined, - toString: function() { - return this.location; - } - }; - - if (!isPossibleLeak(item)) - return; - - compartments[matches[1]] = item; - return; - } - - if ((matches = windowRegexp.exec(path))) { - if (matches[1] in windows) - return; - - let details = windowDetails.exec(matches[1]); - if (!details) { - console.error("Unable to parse window detail " + matches[1]); - return; - } - - let item = { - path: matches[1], - location: details[1].replace(/\\/g, "/"), - source: [details[1].replace(/\\/g, "/")], - toString: function() { - return this.location; - } - }; - - if (!isPossibleLeak(item)) - return; - - windows[matches[1]] = item; - } - } - - Cc["@mozilla.org/memory-reporter-manager;1"] - .getService(Ci.nsIMemoryReporterManager) - .getReportsForThisProcess(logReporter, null, /* anonymize = */ false); - - return { compartments: compartments, windows: windows }; -} - -function nextIteration(tests) { - if (tests) { - results.passed += tests.passed; - results.failed += tests.failed; - - reportMemoryUsage().then(_ => { - let testRun = []; - for (let test of tests.testRunSummary) { - let testCopy = {}; - for (let info in test) { - testCopy[info] = test[info]; - } - testRun.push(testCopy); - } - - results.testRuns.push(testRun); - iterationsLeft--; - - checkForEnd(); - }) - } - else { - checkForEnd(); - } -} - -function checkForEnd() { - if (iterationsLeft && (!stopOnError || results.failed == 0)) { - // Pass the loader which has a hooked console that doesn't dispatch - // errors to the JS console and avoid firing false alarm in our - // console listener - findAndRunTests(loader, nextIteration); - } - else { - setTimeout(cleanup, 0); - } -} - -var POINTLESS_ERRORS = [ - 'Invalid chrome URI:', - 'OpenGL LayerManager Initialized Succesfully.', - '[JavaScript Error: "TelemetryStopwatch:', - 'reference to undefined property', - '[JavaScript Error: "The character encoding of the HTML document was ' + - 'not declared.', - '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' + - 'native weak map key', - '[JavaScript Warning: "Duplicate resource declaration for', - 'file: "chrome://browser/content/', - 'file: "chrome://global/content/', - '[JavaScript Warning: "The character encoding of a framed document was ' + - 'not declared.', - 'file: "chrome://browser/skin/' -]; - -// These are messages that will cause a test to fail if logged through the -// console service -var IMPORTANT_ERRORS = [ - 'Sending message that cannot be cloned. Are you trying to send an XPCOM object?', -]; - -var consoleListener = { - registered: false, - - register: function() { - if (this.registered) - return; - cService.registerListener(this); - this.registered = true; - }, - - unregister: function() { - if (!this.registered) - return; - cService.unregisterListener(this); - this.registered = false; - }, - - errorsLogged: 0, - - observe: function(object) { - if (!(object instanceof Ci.nsIScriptError)) - return; - this.errorsLogged++; - var message = object.QueryInterface(Ci.nsIConsoleMessage).message; - if (IMPORTANT_ERRORS.find(msg => message.indexOf(msg) >= 0)) { - testConsole.error(message); - return; - } - var pointless = POINTLESS_ERRORS.filter(err => message.indexOf(err) >= 0); - if (pointless.length == 0 && message) - testConsole.log(message); - } -}; - -function TestRunnerConsole(base, options) { - let proto = extend(base, { - errorsLogged: 0, - warn: function warn() { - this.errorsLogged++; - base.warn.apply(base, arguments); - }, - error: function error() { - this.errorsLogged++; - base.error.apply(base, arguments); - }, - info: function info(first) { - if (options.verbose) - base.info.apply(base, arguments); - else - if (first == "pass:") - print("."); - }, - }); - return Object.create(proto); -} - -function stringify(arg) { - try { - return String(arg); - } - catch(ex) { - return "<toString() error>"; - } -} - -function stringifyArgs(args) { - return Array.map(args, stringify).join(" "); -} - -function TestRunnerTinderboxConsole(base, options) { - this.base = base; - this.print = options.print; - this.verbose = options.verbose; - this.errorsLogged = 0; - - // Binding all the public methods to an instance so that they can be used - // as callback / listener functions straightaway. - this.log = this.log.bind(this); - this.info = this.info.bind(this); - this.warn = this.warn.bind(this); - this.error = this.error.bind(this); - this.debug = this.debug.bind(this); - this.exception = this.exception.bind(this); - this.trace = this.trace.bind(this); -}; - -TestRunnerTinderboxConsole.prototype = { - testMessage: function testMessage(pass, expected, test, message) { - let type = "TEST-"; - if (expected) { - if (pass) - type += "PASS"; - else - type += "KNOWN-FAIL"; - } - else { - this.errorsLogged++; - if (pass) - type += "UNEXPECTED-PASS"; - else - type += "UNEXPECTED-FAIL"; - } - - this.print(type + " | " + test + " | " + message + "\n"); - if (!expected) - this.trace(); - }, - - log: function log() { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - info: function info(first) { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - warn: function warn() { - this.errorsLogged++; - this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n"); - }, - - error: function error() { - this.errorsLogged++; - this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n"); - this.base.error.apply(this.base, arguments); - }, - - debug: function debug() { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - exception: function exception(e) { - this.print("An exception occurred.\n" + - require("../console/traceback").format(e) + "\n" + e + "\n"); - }, - - trace: function trace() { - var traceback = require("../console/traceback"); - var stack = traceback.get(); - stack.splice(-1, 1); - this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n"); - } -}; - -var runTests = exports.runTests = function runTests(options) { - iterationsLeft = options.iterations; - profileMemory = options.profileMemory; - stopOnError = options.stopOnError; - onDone = options.onDone; - print = options.print; - findAndRunTests = options.findAndRunTests; - - results = { - passed: 0, - failed: 0, - testRuns: [] - }; - - try { - consoleListener.register(); - print("Running tests on " + system.name + " " + system.version + - "/Gecko " + system.platformVersion + " (Build " + - system.build + ") (" + system.id + ") under " + - system.platform + "/" + system.architecture + ".\n"); - - if (options.parseable) - testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options); - else - testConsole = new TestRunnerConsole(new PlainTextConsole(), options); - - loader = Loader(module, { - console: testConsole, - global: {} // useful for storing things like coverage testing. - }); - - // Load these before getting initial leak stats as they will still be in - // memory when we check later - require("../deprecated/unit-test"); - require("../deprecated/unit-test-finder"); - if (profileMemory) - startLeaks = getPotentialLeaks(); - - nextIteration(); - } catch (e) { - let frames = fromException(e).reverse().reduce(function(frames, frame) { - if (frame.fileName.split("/").pop() === "unit-test-finder.js") - frames.done = true - if (!frames.done) frames.push(frame) - - return frames - }, []) - - let prototype = typeof(e) === "object" ? e.constructor.prototype : - Error.prototype; - let stack = serializeStack(frames.reverse()); - - let error = Object.create(prototype, { - message: { value: e.message, writable: true, configurable: true }, - fileName: { value: e.fileName, writable: true, configurable: true }, - lineNumber: { value: e.lineNumber, writable: true, configurable: true }, - stack: { value: stack, writable: true, configurable: true }, - toString: { value: () => String(e), writable: true, configurable: true }, - }); - - print("Error: " + error + " \n " + format(error)); - onDone({passed: 0, failed: 1}); - } -}; - -unload(_ => consoleListener.unregister()); diff --git a/addon-sdk/source/lib/sdk/test/httpd.js b/addon-sdk/source/lib/sdk/test/httpd.js deleted file mode 100644 index 218493924..000000000 --- a/addon-sdk/source/lib/sdk/test/httpd.js +++ /dev/null @@ -1,6 +0,0 @@ -/* 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/. */ - -throw new Error(`This file was removed. A copy can be obtained from: - https://github.com/mozilla/addon-sdk/blob/master/test/lib/httpd.js`); diff --git a/addon-sdk/source/lib/sdk/test/loader.js b/addon-sdk/source/lib/sdk/test/loader.js deleted file mode 100644 index 33ba2ca5a..000000000 --- a/addon-sdk/source/lib/sdk/test/loader.js +++ /dev/null @@ -1,123 +0,0 @@ -/* 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 { resolveURI, Require, - unload, override, descriptor } = require('../../toolkit/loader'); -const { ensure } = require('../system/unload'); -const addonWindow = require('../addon/window'); -const { PlainTextConsole } = require('sdk/console/plain-text'); - -var defaultGlobals = override(require('../system/globals'), { - console: console -}); - -function CustomLoader(module, globals, packaging, overrides={}) { - let options = packaging || require("@loader/options"); - options = override(options, { - id: overrides.id || options.id, - globals: override(defaultGlobals, globals || {}), - modules: override(override(options.modules || {}, overrides.modules || {}), { - 'sdk/addon/window': addonWindow - }) - }); - - let loaderModule = options.isNative ? '../../toolkit/loader' : '../loader/cuddlefish'; - let { Loader } = require(loaderModule); - let loader = Loader(options); - let wrapper = Object.create(loader, descriptor({ - require: Require(loader, module), - sandbox: function(id) { - let requirement = loader.resolve(id, module.id); - if (!requirement) - requirement = id; - let uri = resolveURI(requirement, loader.mapping); - return loader.sandboxes[uri]; - }, - unload: function(reason) { - unload(loader, reason); - } - })); - ensure(wrapper); - return wrapper; -}; -exports.Loader = CustomLoader; - -function HookedPlainTextConsole(hook, print, innerID) { - this.log = hook.bind(null, "log", innerID); - this.info = hook.bind(null, "info", innerID); - this.warn = hook.bind(null, "warn", innerID); - this.error = hook.bind(null, "error", innerID); - this.debug = hook.bind(null, "debug", innerID); - this.exception = hook.bind(null, "exception", innerID); - this.time = hook.bind(null, "time", innerID); - this.timeEnd = hook.bind(null, "timeEnd", innerID); - - this.__exposedProps__ = { - log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw", - exception: "rw", time: "rw", timeEnd: "rw" - }; -} - -// Creates a custom loader instance whose console module is hooked in order -// to avoid printing messages to the console, and instead, expose them in the -// returned `messages` array attribute -exports.LoaderWithHookedConsole = function (module, callback) { - let messages = []; - function hook(type, innerID, msg) { - messages.push({ type: type, msg: msg, innerID: innerID }); - if (callback) - callback(type, msg, innerID); - } - - return { - loader: CustomLoader(module, { - console: new HookedPlainTextConsole(hook, null, null) - }, null, { - modules: { - 'sdk/console/plain-text': { - PlainTextConsole: HookedPlainTextConsole.bind(null, hook) - } - } - }), - messages: messages - }; -} - -// Same than LoaderWithHookedConsole with lower level, instead we get what is -// actually printed to the command line console -exports.LoaderWithHookedConsole2 = function (module, callback) { - let messages = []; - return { - loader: CustomLoader(module, { - console: new PlainTextConsole(function (msg) { - messages.push(msg); - if (callback) - callback(msg); - }) - }), - messages: messages - }; -} - -// Creates a custom loader with a filtered console. The callback is passed every -// console message type and message and if it returns false the message will -// not be logged normally -exports.LoaderWithFilteredConsole = function (module, callback) { - function hook(type, innerID, msg) { - if (callback && callback(type, msg, innerID) == false) - return; - console[type](msg); - } - - return CustomLoader(module, { - console: new HookedPlainTextConsole(hook, null, null) - }, null, { - modules: { - 'sdk/console/plain-text': { - PlainTextConsole: HookedPlainTextConsole.bind(null, hook) - } - } - }); -} diff --git a/addon-sdk/source/lib/sdk/test/memory.js b/addon-sdk/source/lib/sdk/test/memory.js deleted file mode 100644 index bd1198bfe..000000000 --- a/addon-sdk/source/lib/sdk/test/memory.js +++ /dev/null @@ -1,11 +0,0 @@ -/* 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 { Cu } = require("chrome"); - -function gc() { - return new Promise(resolve => Cu.schedulePreciseGC(resolve)); -} -exports.gc = gc; diff --git a/addon-sdk/source/lib/sdk/test/options.js b/addon-sdk/source/lib/sdk/test/options.js deleted file mode 100644 index 9bc611ca5..000000000 --- a/addon-sdk/source/lib/sdk/test/options.js +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 options = require("@test/options"); -const { id } = require("../self"); -const { get } = require("../preferences/service"); - -const readPref = (key) => get("extensions." + id + ".sdk." + key); - -exports.iterations = readPref("test.iterations") || options.iterations; -exports.filter = readPref("test.filter") || options.filter; -exports.profileMemory = readPref("profile.memory") || options.profileMemory; -exports.stopOnError = readPref("test.stop") || options.stopOnError; -exports.keepOpen = readPref("test.keepOpen") || false; -exports.verbose = (readPref("output.logLevel") == "verbose") || options.verbose; -exports.parseable = (readPref("output.format") == "tbpl") || options.parseable; -exports.checkMemory = readPref("profile.leaks") || options.check_memory; diff --git a/addon-sdk/source/lib/sdk/test/runner.js b/addon-sdk/source/lib/sdk/test/runner.js deleted file mode 100644 index ea37ac84f..000000000 --- a/addon-sdk/source/lib/sdk/test/runner.js +++ /dev/null @@ -1,131 +0,0 @@ -/* 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": "experimental" -}; - -var { exit, stdout } = require("../system"); -var cfxArgs = require("../test/options"); -var events = require("../system/events"); -const { resolve } = require("../core/promise"); - -function runTests(findAndRunTests) { - var harness = require("./harness"); - - function onDone(tests) { - stdout.write("\n"); - var total = tests.passed + tests.failed; - stdout.write(tests.passed + " of " + total + " tests passed.\n"); - - events.emit("sdk:test:results", { data: JSON.stringify(tests) }); - - if (tests.failed == 0) { - if (tests.passed === 0) - stdout.write("No tests were run\n"); - if (!cfxArgs.keepOpen) - exit(0); - } else { - if (cfxArgs.verbose || cfxArgs.parseable) - printFailedTests(tests, stdout.write); - if (!cfxArgs.keepOpen) - exit(1); - } - }; - - // We may have to run test on next cycle, otherwise XPCOM components - // are not correctly updated. - // For ex: nsIFocusManager.getFocusedElementForWindow may throw - // NS_ERROR_ILLEGAL_VALUE exception. - require("../timers").setTimeout(_ => harness.runTests({ - findAndRunTests: findAndRunTests, - iterations: cfxArgs.iterations || 1, - filter: cfxArgs.filter, - profileMemory: cfxArgs.profileMemory, - stopOnError: cfxArgs.stopOnError, - verbose: cfxArgs.verbose, - parseable: cfxArgs.parseable, - print: stdout.write, - onDone: onDone - })); -} - -function printFailedTests(tests, print) { - let iterationNumber = 0; - let singleIteration = (tests.testRuns || []).length == 1; - let padding = singleIteration ? "" : " "; - - print("\nThe following tests failed:\n"); - - for (let testRun of tests.testRuns) { - iterationNumber++; - - if (!singleIteration) - print(" Iteration " + iterationNumber + ":\n"); - - for (let test of testRun) { - if (test.failed > 0) { - print(padding + " " + test.name + ": " + test.errors +"\n"); - } - } - print("\n"); - } -} - -function main() { - var testsStarted = false; - - if (!testsStarted) { - testsStarted = true; - runTests(function findAndRunTests(loader, nextIteration) { - loader.require("../deprecated/unit-test").findAndRunTests({ - testOutOfProcess: false, - testInProcess: true, - stopOnError: cfxArgs.stopOnError, - filter: cfxArgs.filter, - onDone: nextIteration - }); - }); - } -}; - -if (require.main === module) - main(); - -exports.runTestsFromModule = function runTestsFromModule(module) { - let id = module.id; - // Make a copy of exports as it may already be frozen by module loader - let exports = {}; - Object.keys(module.exports).forEach(key => { - exports[key] = module.exports[key]; - }); - - runTests(function findAndRunTests(loader, nextIteration) { - // Consider that all these tests are CommonJS ones - loader.require('../../test').run(exports); - - // Reproduce what is done in sdk/deprecated/unit-test-finder.findTests() - let tests = []; - for (let name of Object.keys(exports).sort()) { - tests.push({ - setup: exports.setup, - teardown: exports.teardown, - testFunction: exports[name], - name: id + "." + name - }); - } - - // Reproduce what is done by unit-test.findAndRunTests() - var { TestRunner } = loader.require("../deprecated/unit-test"); - var runner = new TestRunner(); - runner.startMany({ - tests: { - getNext: () => resolve(tests.shift()) - }, - stopOnError: cfxArgs.stopOnError, - onDone: nextIteration - }); - }); -} diff --git a/addon-sdk/source/lib/sdk/test/utils.js b/addon-sdk/source/lib/sdk/test/utils.js deleted file mode 100644 index b01df67d4..000000000 --- a/addon-sdk/source/lib/sdk/test/utils.js +++ /dev/null @@ -1,199 +0,0 @@ -/* 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 { defer } = require('../core/promise'); -const { setInterval, clearInterval } = require('../timers'); -const { getTabs, closeTab } = require("../tabs/utils"); -const { windows: getWindows } = require("../window/utils"); -const { close: closeWindow } = require("../window/helpers"); -const { isGenerator } = require("../lang/type"); -const { env } = require("../system/environment"); -const { Task } = require("resource://gre/modules/Task.jsm"); - -const getTestNames = (exports) => - Object.keys(exports).filter(name => /^test/.test(name)); - -const isTestAsync = ({length}) => length > 1; -const isHelperAsync = ({length}) => length > 2; - -/* - * Takes an `exports` object of a test file and a function `beforeFn` - * to be run before each test. `beforeFn` is called with a `name` string - * as the first argument of the test name, and may specify a second - * argument function `done` to indicate that this function should - * resolve asynchronously - */ -function before (exports, beforeFn) { - getTestNames(exports).map(name => { - let testFn = exports[name]; - - // GENERATOR TESTS - if (isGenerator(testFn) && isGenerator(beforeFn)) { - exports[name] = function*(assert) { - yield Task.spawn(beforeFn.bind(null, name, assert)); - yield Task.spawn(testFn.bind(null, assert)); - } - } - else if (isGenerator(testFn) && !isHelperAsync(beforeFn)) { - exports[name] = function*(assert) { - beforeFn(name, assert); - yield Task.spawn(testFn.bind(null, assert)); - } - } - else if (isGenerator(testFn) && isHelperAsync(beforeFn)) { - exports[name] = function*(assert) { - yield new Promise(resolve => beforeFn(name, assert, resolve)); - yield Task.spawn(testFn.bind(null, assert)); - } - } - // SYNC TESTS - else if (!isTestAsync(testFn) && isGenerator(beforeFn)) { - exports[name] = function*(assert) { - yield Task.spawn(beforeFn.bind(null, name, assert)); - testFn(assert); - }; - } - else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) { - exports[name] = function (assert) { - beforeFn(name, assert); - testFn(assert); - }; - } - else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) { - exports[name] = function (assert, done) { - beforeFn(name, assert, () => { - testFn(assert); - done(); - }); - }; - } - // ASYNC TESTS - else if (isTestAsync(testFn) && isGenerator(beforeFn)) { - exports[name] = function*(assert) { - yield Task.spawn(beforeFn.bind(null, name, assert)); - yield new Promise(resolve => testFn(assert, resolve)); - }; - } - else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) { - exports[name] = function (assert, done) { - beforeFn(name, assert); - testFn(assert, done); - }; - } - else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) { - exports[name] = function (assert, done) { - beforeFn(name, assert, () => { - testFn(assert, done); - }); - }; - } - }); -} -exports.before = before; - -/* - * Takes an `exports` object of a test file and a function `afterFn` - * to be run after each test. `afterFn` is called with a `name` string - * as the first argument of the test name, and may specify a second - * argument function `done` to indicate that this function should - * resolve asynchronously - */ -function after (exports, afterFn) { - getTestNames(exports).map(name => { - let testFn = exports[name]; - - // GENERATOR TESTS - if (isGenerator(testFn) && isGenerator(afterFn)) { - exports[name] = function*(assert) { - yield Task.spawn(testFn.bind(null, assert)); - yield Task.spawn(afterFn.bind(null, name, assert)); - } - } - else if (isGenerator(testFn) && !isHelperAsync(afterFn)) { - exports[name] = function*(assert) { - yield Task.spawn(testFn.bind(null, assert)); - afterFn(name, assert); - } - } - else if (isGenerator(testFn) && isHelperAsync(afterFn)) { - exports[name] = function*(assert) { - yield Task.spawn(testFn.bind(null, assert)); - yield new Promise(resolve => afterFn(name, assert, resolve)); - } - } - // SYNC TESTS - else if (!isTestAsync(testFn) && isGenerator(afterFn)) { - exports[name] = function*(assert) { - testFn(assert); - yield Task.spawn(afterFn.bind(null, name, assert)); - }; - } - else if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) { - exports[name] = function (assert) { - testFn(assert); - afterFn(name, assert); - }; - } - else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) { - exports[name] = function (assert, done) { - testFn(assert); - afterFn(name, assert, done); - }; - } - // ASYNC TESTS - else if (isTestAsync(testFn) && isGenerator(afterFn)) { - exports[name] = function*(assert) { - yield new Promise(resolve => testFn(assert, resolve)); - yield Task.spawn(afterFn.bind(null, name, assert)); - }; - } - else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) { - exports[name] = function*(assert) { - yield new Promise(resolve => testFn(assert, resolve)); - afterFn(name, assert); - }; - } - else if (isTestAsync(testFn) && isHelperAsync(afterFn)) { - exports[name] = function*(assert) { - yield new Promise(resolve => testFn(assert, resolve)); - yield new Promise(resolve => afterFn(name, assert, resolve)); - }; - } - }); -} -exports.after = after; - -function waitUntil (predicate, delay) { - let { promise, resolve } = defer(); - let interval = setInterval(() => { - if (!predicate()) return; - clearInterval(interval); - resolve(); - }, delay || 10); - return promise; -} -exports.waitUntil = waitUntil; - -var cleanUI = function cleanUI() { - let { promise, resolve } = defer(); - - let windows = getWindows(null, { includePrivate: true }); - if (windows.length > 1) { - return closeWindow(windows[1]).then(cleanUI); - } - - getTabs(windows[0]).slice(1).forEach(closeTab); - - resolve(); - - return promise; -} -exports.cleanUI = cleanUI; - -exports.isTravisCI = ("TRAVIS" in env && "CI" in env); diff --git a/addon-sdk/source/lib/sdk/timers.js b/addon-sdk/source/lib/sdk/timers.js deleted file mode 100644 index e97db01f2..000000000 --- a/addon-sdk/source/lib/sdk/timers.js +++ /dev/null @@ -1,105 +0,0 @@ -/* 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": "stable" -}; - -const { CC, Cc, Ci } = require("chrome"); -const { when: unload } = require("./system/unload"); - -const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer; -const Timer = CC("@mozilla.org/timer;1", "nsITimer"); -const timers = Object.create(null); -const threadManager = Cc["@mozilla.org/thread-manager;1"]. - getService(Ci.nsIThreadManager); -const prefBranch = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch); - -var MIN_DELAY = 4; -// Try to get min timeout delay used by browser. -try { MIN_DELAY = prefBranch.getIntPref("dom.min_timeout_value"); } finally {} - - -// Last timer id. -var lastID = 0; - -// Sets typer either by timeout or by interval -// depending on a given type. -function setTimer(type, callback, delay, ...args) { - let id = ++ lastID; - let timer = timers[id] = Timer(); - timer.initWithCallback({ - notify: function notify() { - try { - if (type === TYPE_ONE_SHOT) - delete timers[id]; - callback.apply(null, args); - } - catch(error) { - console.exception(error); - } - } - }, Math.max(delay || MIN_DELAY), type); - return id; -} - -function unsetTimer(id) { - let timer = timers[id]; - delete timers[id]; - if (timer) timer.cancel(); -} - -var immediates = new Map(); - -var dispatcher = _ => { - // Allow scheduling of a new dispatch loop. - dispatcher.scheduled = false; - // Take a snapshot of timer `id`'s that have being present before - // starting a dispatch loop, in order to ignore timers registered - // in side effect to dispatch while also skipping immediates that - // were removed in side effect. - let ids = [...immediates.keys()]; - for (let id of ids) { - let immediate = immediates.get(id); - if (immediate) { - immediates.delete(id); - try { immediate(); } - catch (error) { console.exception(error); } - } - } -} - -function setImmediate(callback, ...params) { - let id = ++ lastID; - // register new immediate timer with curried params. - immediates.set(id, _ => callback.apply(callback, params)); - // if dispatch loop is not scheduled schedule one. Own scheduler - if (!dispatcher.scheduled) { - dispatcher.scheduled = true; - threadManager.currentThread.dispatch(dispatcher, - Ci.nsIThread.DISPATCH_NORMAL); - } - return id; -} - -function clearImmediate(id) { - immediates.delete(id); -} - -// Bind timers so that toString-ing them looks same as on native timers. -exports.setImmediate = setImmediate.bind(null); -exports.clearImmediate = clearImmediate.bind(null); -exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT); -exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK); -exports.clearTimeout = unsetTimer.bind(null); -exports.clearInterval = unsetTimer.bind(null); - -// all timers are cleared out on unload. -unload(function() { - immediates.clear(); - Object.keys(timers).forEach(unsetTimer) -}); diff --git a/addon-sdk/source/lib/sdk/ui.js b/addon-sdk/source/lib/sdk/ui.js deleted file mode 100644 index 7f9110b26..000000000 --- a/addon-sdk/source/lib/sdk/ui.js +++ /dev/null @@ -1,17 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '> 28' - } -}; - -exports.ActionButton = require('./ui/button/action').ActionButton; -exports.ToggleButton = require('./ui/button/toggle').ToggleButton; -exports.Sidebar = require('./ui/sidebar').Sidebar; -exports.Frame = require('./ui/frame').Frame; -exports.Toolbar = require('./ui/toolbar').Toolbar; diff --git a/addon-sdk/source/lib/sdk/ui/button/action.js b/addon-sdk/source/lib/sdk/ui/button/action.js deleted file mode 100644 index dfb092d0c..000000000 --- a/addon-sdk/source/lib/sdk/ui/button/action.js +++ /dev/null @@ -1,114 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '> 28' - } -}; - -const { Class } = require('../../core/heritage'); -const { merge } = require('../../util/object'); -const { Disposable } = require('../../core/disposable'); -const { on, off, emit, setListeners } = require('../../event/core'); -const { EventTarget } = require('../../event/target'); -const { getNodeView } = require('../../view/core'); - -const view = require('./view'); -const { buttonContract, stateContract } = require('./contract'); -const { properties, render, state, register, unregister, - getDerivedStateFor } = require('../state'); -const { events: stateEvents } = require('../state/events'); -const { events: viewEvents } = require('./view/events'); -const events = require('../../event/utils'); - -const { getActiveTab } = require('../../tabs/utils'); - -const { id: addonID } = require('../../self'); -const { identify } = require('../id'); - -const buttons = new Map(); - -const toWidgetId = id => - ('action-button--' + addonID.toLowerCase()+ '-' + id). - replace(/[^a-z0-9_-]/g, ''); - -const ActionButton = Class({ - extends: EventTarget, - implements: [ - properties(stateContract), - state(stateContract), - Disposable - ], - setup: function setup(options) { - let state = merge({ - disabled: false - }, buttonContract(options)); - - let id = toWidgetId(options.id); - - register(this, state); - - // Setup listeners. - setListeners(this, options); - - buttons.set(id, this); - - view.create(merge({}, state, { id: id })); - }, - - dispose: function dispose() { - let id = toWidgetId(this.id); - buttons.delete(id); - - off(this); - - view.dispose(id); - - unregister(this); - }, - - get id() { - return this.state().id; - }, - - click: function click() { view.click(toWidgetId(this.id)) } -}); -exports.ActionButton = ActionButton; - -identify.define(ActionButton, ({id}) => toWidgetId(id)); - -getNodeView.define(ActionButton, button => - view.nodeFor(toWidgetId(button.id)) -); - -var actionButtonStateEvents = events.filter(stateEvents, - e => e.target instanceof ActionButton); - -var actionButtonViewEvents = events.filter(viewEvents, - e => buttons.has(e.target)); - -var clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click'); -var updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update'); - -on(clickEvents, 'data', ({target: id, window}) => { - let button = buttons.get(id); - let state = getDerivedStateFor(button, getActiveTab(window)); - - emit(button, 'click', state); -}); - -on(updateEvents, 'data', ({target: id, window}) => { - render(buttons.get(id), window); -}); - -on(actionButtonStateEvents, 'data', ({target, window, state}) => { - let id = toWidgetId(target.id); - view.setIcon(id, window, state.icon); - view.setLabel(id, window, state.label); - view.setDisabled(id, window, state.disabled); - view.setBadge(id, window, state.badge, state.badgeColor); -}); diff --git a/addon-sdk/source/lib/sdk/ui/button/contract.js b/addon-sdk/source/lib/sdk/ui/button/contract.js deleted file mode 100644 index ce6e33d95..000000000 --- a/addon-sdk/source/lib/sdk/ui/button/contract.js +++ /dev/null @@ -1,73 +0,0 @@ -/* 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 { contract } = require('../../util/contract'); -const { isLocalURL } = require('../../url'); -const { isNil, isObject, isString } = require('../../lang/type'); -const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils'); -const { merge } = require('../../util/object'); -const { freeze } = Object; - -const isIconSet = (icons) => - Object.keys(icons). - every(size => String(size >>> 0) === size && isLocalURL(icons[size])); - -var iconSet = { - is: either(object, string), - map: v => isObject(v) ? freeze(merge({}, v)) : v, - ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)), - msg: 'The option "icon" must be a local URL or an object with ' + - 'numeric keys / local URL values pair.' -} - -var id = { - is: string, - ok: v => /^[a-z-_][a-z0-9-_]*$/i.test(v), - msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' + - 'underscores are allowed).' -}; - -var label = { - is: string, - ok: v => isNil(v) || v.trim().length > 0, - msg: 'The option "label" must be a non empty string' -} - -var badge = { - is: either(string, number), - msg: 'The option "badge" must be a string or a number' -} - -var badgeColor = { - is: string, - msg: 'The option "badgeColor" must be a string' -} - -var stateContract = contract({ - label: label, - icon: iconSet, - disabled: boolean, - badge: badge, - badgeColor: badgeColor -}); - -exports.stateContract = stateContract; - -var buttonContract = contract(merge({}, stateContract.rules, { - id: required(id), - label: required(label), - icon: required(iconSet) -})); - -exports.buttonContract = buttonContract; - -exports.toggleStateContract = contract(merge({ - checked: boolean -}, stateContract.rules)); - -exports.toggleButtonContract = contract(merge({ - checked: boolean -}, buttonContract.rules)); - diff --git a/addon-sdk/source/lib/sdk/ui/button/toggle.js b/addon-sdk/source/lib/sdk/ui/button/toggle.js deleted file mode 100644 index a226b3212..000000000 --- a/addon-sdk/source/lib/sdk/ui/button/toggle.js +++ /dev/null @@ -1,127 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '> 28' - } -}; - -const { Class } = require('../../core/heritage'); -const { merge } = require('../../util/object'); -const { Disposable } = require('../../core/disposable'); -const { on, off, emit, setListeners } = require('../../event/core'); -const { EventTarget } = require('../../event/target'); -const { getNodeView } = require('../../view/core'); - -const view = require('./view'); -const { toggleButtonContract, toggleStateContract } = require('./contract'); -const { properties, render, state, register, unregister, - setStateFor, getStateFor, getDerivedStateFor } = require('../state'); -const { events: stateEvents } = require('../state/events'); -const { events: viewEvents } = require('./view/events'); -const events = require('../../event/utils'); - -const { getActiveTab } = require('../../tabs/utils'); - -const { id: addonID } = require('../../self'); -const { identify } = require('../id'); - -const buttons = new Map(); - -const toWidgetId = id => - ('toggle-button--' + addonID.toLowerCase()+ '-' + id). - replace(/[^a-z0-9_-]/g, ''); - -const ToggleButton = Class({ - extends: EventTarget, - implements: [ - properties(toggleStateContract), - state(toggleStateContract), - Disposable - ], - setup: function setup(options) { - let state = merge({ - disabled: false, - checked: false - }, toggleButtonContract(options)); - - let id = toWidgetId(options.id); - - register(this, state); - - // Setup listeners. - setListeners(this, options); - - buttons.set(id, this); - - view.create(merge({ type: 'checkbox' }, state, { id: id })); - }, - - dispose: function dispose() { - let id = toWidgetId(this.id); - buttons.delete(id); - - off(this); - - view.dispose(id); - - unregister(this); - }, - - get id() { - return this.state().id; - }, - - click: function click() { - return view.click(toWidgetId(this.id)); - } -}); -exports.ToggleButton = ToggleButton; - -identify.define(ToggleButton, ({id}) => toWidgetId(id)); - -getNodeView.define(ToggleButton, button => - view.nodeFor(toWidgetId(button.id)) -); - -var toggleButtonStateEvents = events.filter(stateEvents, - e => e.target instanceof ToggleButton); - -var toggleButtonViewEvents = events.filter(viewEvents, - e => buttons.has(e.target)); - -var clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click'); -var updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update'); - -on(toggleButtonStateEvents, 'data', ({target, window, state}) => { - let id = toWidgetId(target.id); - - view.setIcon(id, window, state.icon); - view.setLabel(id, window, state.label); - view.setDisabled(id, window, state.disabled); - view.setChecked(id, window, state.checked); - view.setBadge(id, window, state.badge, state.badgeColor); -}); - -on(clickEvents, 'data', ({target: id, window, checked }) => { - let button = buttons.get(id); - let windowState = getStateFor(button, window); - - let newWindowState = merge({}, windowState, { checked: checked }); - - setStateFor(button, window, newWindowState); - - let state = getDerivedStateFor(button, getActiveTab(window)); - - emit(button, 'click', state); - - emit(button, 'change', state); -}); - -on(updateEvents, 'data', ({target: id, window}) => { - render(buttons.get(id), window); -}); diff --git a/addon-sdk/source/lib/sdk/ui/button/view.js b/addon-sdk/source/lib/sdk/ui/button/view.js deleted file mode 100644 index 63b7aea31..000000000 --- a/addon-sdk/source/lib/sdk/ui/button/view.js +++ /dev/null @@ -1,243 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '> 28' - } -}; - -const { Cu } = require('chrome'); -const { on, off, emit } = require('../../event/core'); - -const { data } = require('sdk/self'); - -const { isObject, isNil } = require('../../lang/type'); - -const { getMostRecentBrowserWindow } = require('../../window/utils'); -const { ignoreWindow } = require('../../private-browsing/utils'); -const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); -const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI; - -const { events: viewEvents } = require('./view/events'); - -const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; - -const views = new Map(); -const customizedWindows = new WeakMap(); - -const buttonListener = { - onCustomizeStart: window => { - for (let [id, view] of views) { - setIcon(id, window, view.icon); - setLabel(id, window, view.label); - } - - customizedWindows.set(window, true); - }, - onCustomizeEnd: window => { - customizedWindows.delete(window); - - for (let [id, ] of views) { - let placement = CustomizableUI.getPlacementOfWidget(id); - - if (placement) - emit(viewEvents, 'data', { type: 'update', target: id, window: window }); - } - }, - onWidgetAfterDOMChange: (node, nextNode, container) => { - let { id } = node; - let view = views.get(id); - let window = node.ownerDocument.defaultView; - - if (view) { - emit(viewEvents, 'data', { type: 'update', target: id, window: window }); - } - } -}; - -CustomizableUI.addListener(buttonListener); - -require('../../system/unload').when( _ => - CustomizableUI.removeListener(buttonListener) -); - -function getNode(id, window) { - return !views.has(id) || ignoreWindow(window) - ? null - : CustomizableUI.getWidget(id).forWindow(window).node -}; - -function isInToolbar(id) { - let placement = CustomizableUI.getPlacementOfWidget(id); - - return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar'; -} - - -function getImage(icon, isInToolbar, pixelRatio) { - let targetSize = (isInToolbar ? 18 : 32) * pixelRatio; - let bestSize = 0; - let image = icon; - - if (isObject(icon)) { - for (let size of Object.keys(icon)) { - size = +size; - let offset = targetSize - size; - - if (offset === 0) { - bestSize = size; - break; - } - - let delta = Math.abs(offset) - Math.abs(targetSize - bestSize); - - if (delta < 0) - bestSize = size; - } - - image = icon[bestSize]; - } - - if (image.indexOf('./') === 0) - return data.url(image.substr(2)); - - return image; -} - -function nodeFor(id, window=getMostRecentBrowserWindow()) { - return customizedWindows.has(window) ? null : getNode(id, window); -}; -exports.nodeFor = nodeFor; - -function create(options) { - let { id, label, icon, type, badge } = options; - - if (views.has(id)) - throw new Error('The ID "' + id + '" seems already used.'); - - CustomizableUI.createWidget({ - id: id, - type: 'custom', - removable: true, - defaultArea: AREA_NAVBAR, - allowedAreas: [ AREA_PANEL, AREA_NAVBAR ], - - onBuild: function(document) { - let window = document.defaultView; - - let node = document.createElementNS(XUL_NS, 'toolbarbutton'); - - let image = getImage(icon, true, window.devicePixelRatio); - - if (ignoreWindow(window)) - node.style.display = 'none'; - - node.setAttribute('id', this.id); - node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button'); - node.setAttribute('type', type); - node.setAttribute('label', label); - node.setAttribute('tooltiptext', label); - node.setAttribute('image', image); - node.setAttribute('constrain-size', 'true'); - - views.set(id, { - area: this.currentArea, - icon: icon, - label: label - }); - - node.addEventListener('command', function(event) { - if (views.has(id)) { - emit(viewEvents, 'data', { - type: 'click', - target: id, - window: event.view, - checked: node.checked - }); - } - }); - - return node; - } - }); -}; -exports.create = create; - -function dispose(id) { - if (!views.has(id)) return; - - views.delete(id); - CustomizableUI.destroyWidget(id); -} -exports.dispose = dispose; - -function setIcon(id, window, icon) { - let node = getNode(id, window); - - if (node) { - icon = customizedWindows.has(window) ? views.get(id).icon : icon; - let image = getImage(icon, isInToolbar(id), window.devicePixelRatio); - - node.setAttribute('image', image); - } -} -exports.setIcon = setIcon; - -function setLabel(id, window, label) { - let node = nodeFor(id, window); - - if (node) { - node.setAttribute('label', label); - node.setAttribute('tooltiptext', label); - } -} -exports.setLabel = setLabel; - -function setDisabled(id, window, disabled) { - let node = nodeFor(id, window); - - if (node) - node.disabled = disabled; -} -exports.setDisabled = setDisabled; - -function setChecked(id, window, checked) { - let node = nodeFor(id, window); - - if (node) - node.checked = checked; -} -exports.setChecked = setChecked; - -function setBadge(id, window, badge, color) { - let node = nodeFor(id, window); - - if (node) { - // `Array.from` is needed to handle unicode symbol properly: - // '𝐀𝐁'.length is 4 where Array.from('𝐀𝐁').length is 2 - let text = isNil(badge) - ? '' - : Array.from(String(badge)).slice(0, 4).join(''); - - node.setAttribute('badge', text); - - let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node, - 'class', 'toolbarbutton-badge'); - - if (badgeNode) - badgeNode.style.backgroundColor = isNil(color) ? '' : color; - } -} -exports.setBadge = setBadge; - -function click(id) { - let node = nodeFor(id); - - if (node) - node.click(); -} -exports.click = click; diff --git a/addon-sdk/source/lib/sdk/ui/button/view/events.js b/addon-sdk/source/lib/sdk/ui/button/view/events.js deleted file mode 100644 index 98909656a..000000000 --- a/addon-sdk/source/lib/sdk/ui/button/view/events.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '*', - 'SeaMonkey': '*', - 'Thunderbird': '*' - } -}; - -var channel = {}; - -exports.events = channel; diff --git a/addon-sdk/source/lib/sdk/ui/component.js b/addon-sdk/source/lib/sdk/ui/component.js deleted file mode 100644 index d1f12c95e..000000000 --- a/addon-sdk/source/lib/sdk/ui/component.js +++ /dev/null @@ -1,182 +0,0 @@ -/* 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"; - -// Internal properties not exposed to the public. -const cache = Symbol("component/cache"); -const writer = Symbol("component/writer"); -const isFirstWrite = Symbol("component/writer/first-write?"); -const currentState = Symbol("component/state/current"); -const pendingState = Symbol("component/state/pending"); -const isWriting = Symbol("component/writing?"); - -const isntNull = x => x !== null; - -const Component = function(options, children) { - this[currentState] = null; - this[pendingState] = null; - this[writer] = null; - this[cache] = null; - this[isFirstWrite] = true; - - this[Component.construct](options, children); -} -Component.Component = Component; -// Constructs component. -Component.construct = Symbol("component/construct"); -// Called with `options` and `children` and must return -// initial state back. -Component.initial = Symbol("component/initial"); - -// Function patches current `state` with a given update. -Component.patch = Symbol("component/patch"); -// Function that replaces current `state` with a passed state. -Component.reset = Symbol("component/reset"); - -// Function that must return render tree from passed state. -Component.render = Symbol("component/render"); - -// Path of the component with in the mount point. -Component.path = Symbol("component/path"); - -Component.isMounted = component => !!component[writer]; -Component.isWriting = component => !!component[isWriting]; - -// Internal method that mounts component to a writer. -// Mounts component to a writer. -Component.mount = (component, write) => { - if (Component.isMounted(component)) { - throw Error("Can not mount already mounted component"); - } - - component[writer] = write; - Component.write(component); - - if (component[Component.mounted]) { - component[Component.mounted](); - } -} - -// Unmounts component from a writer. -Component.unmount = (component) => { - if (Component.isMounted(component)) { - component[writer] = null; - if (component[Component.unmounted]) { - component[Component.unmounted](); - } - } else { - console.warn("Unmounting component that is not mounted is redundant"); - } -}; - // Method invoked once after inital write occurs. -Component.mounted = Symbol("component/mounted"); -// Internal method that unmounts component from the writer. -Component.unmounted = Symbol("component/unmounted"); -// Function that must return true if component is changed -Component.isUpdated = Symbol("component/updated?"); -Component.update = Symbol("component/update"); -Component.updated = Symbol("component/updated"); - -const writeChild = base => (child, index) => Component.write(child, base, index) -Component.write = (component, base, index) => { - if (component === null) { - return component; - } - - if (!(component instanceof Component)) { - const path = base ? `${base}${component.key || index}/` : `/`; - return Object.assign({}, component, { - [Component.path]: path, - children: component.children && component.children. - map(writeChild(path)). - filter(isntNull) - }); - } - - component[isWriting] = true; - - try { - - const current = component[currentState]; - const pending = component[pendingState] || current; - const isUpdated = component[Component.isUpdated]; - const isInitial = component[isFirstWrite]; - - if (isUpdated(current, pending) || isInitial) { - if (!isInitial && component[Component.update]) { - component[Component.update](pending, current) - } - - // Note: [Component.update] could have caused more updates so can't use - // `pending` as `component[pendingState]` may have changed. - component[currentState] = component[pendingState] || current; - component[pendingState] = null; - - const tree = component[Component.render](component[currentState]); - component[cache] = Component.write(tree, base, index); - if (component[writer]) { - component[writer].call(null, component[cache]); - } - - if (!isInitial && component[Component.updated]) { - component[Component.updated](current, pending); - } - } - - component[isFirstWrite] = false; - - return component[cache]; - } finally { - component[isWriting] = false; - } -}; - -Component.prototype = Object.freeze({ - constructor: Component, - - [Component.mounted]: null, - [Component.unmounted]: null, - [Component.update]: null, - [Component.updated]: null, - - get state() { - return this[pendingState] || this[currentState]; - }, - - - [Component.construct](settings, items) { - const initial = this[Component.initial]; - const base = initial(settings, items); - const options = Object.assign(Object.create(null), base.options, settings); - const children = base.children || items || null; - const state = Object.assign(Object.create(null), base, {options, children}); - this[currentState] = state; - - if (this.setup) { - this.setup(state); - } - }, - [Component.initial](options, children) { - return Object.create(null); - }, - [Component.patch](update) { - this[Component.reset](Object.assign({}, this.state, update)); - }, - [Component.reset](state) { - this[pendingState] = state; - if (Component.isMounted(this) && !Component.isWriting(this)) { - Component.write(this); - } - }, - - [Component.isUpdated](before, after) { - return before != after - }, - - [Component.render](state) { - throw Error("Component must implement [Component.render] member"); - } -}); - -module.exports = Component; diff --git a/addon-sdk/source/lib/sdk/ui/frame.js b/addon-sdk/source/lib/sdk/ui/frame.js deleted file mode 100644 index 566353cdf..000000000 --- a/addon-sdk/source/lib/sdk/ui/frame.js +++ /dev/null @@ -1,16 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -require("./frame/view"); -const { Frame } = require("./frame/model"); - -exports.Frame = Frame; diff --git a/addon-sdk/source/lib/sdk/ui/frame/model.js b/addon-sdk/source/lib/sdk/ui/frame/model.js deleted file mode 100644 index 627310874..000000000 --- a/addon-sdk/source/lib/sdk/ui/frame/model.js +++ /dev/null @@ -1,154 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -const { Class } = require("../../core/heritage"); -const { EventTarget } = require("../../event/target"); -const { emit, off, setListeners } = require("../../event/core"); -const { Reactor, foldp, send, merges } = require("../../event/utils"); -const { Disposable } = require("../../core/disposable"); -const { OutputPort } = require("../../output/system"); -const { InputPort } = require("../../input/system"); -const { identify } = require("../id"); -const { pairs, object, map, each } = require("../../util/sequence"); -const { patch, diff } = require("diffpatcher/index"); -const { isLocalURL } = require("../../url"); -const { compose } = require("../../lang/functional"); -const { contract } = require("../../util/contract"); -const { id: addonID, data: { url: resolve }} = require("../../self"); -const { Frames } = require("../../input/frame"); - - -const output = new OutputPort({ id: "frame-change" }); -const mailbox = new OutputPort({ id: "frame-mailbox" }); -const input = Frames; - - -const makeID = url => - ("frame-" + addonID + "-" + url). - split("/").join("-"). - split(".").join("-"). - replace(/[^A-Za-z0-9_\-]/g, ""); - -const validate = contract({ - name: { - is: ["string", "undefined"], - ok: x => /^[a-z][a-z0-9-_]+$/i.test(x), - msg: "The `option.name` must be a valid alphanumeric string (hyphens and " + - "underscores are allowed) starting with letter." - }, - url: { - map: x => x.toString(), - is: ["string"], - ok: x => isLocalURL(x), - msg: "The `options.url` must be a valid local URI." - } -}); - -const Source = function({id, ownerID}) { - this.id = id; - this.ownerID = ownerID; -}; -Source.postMessage = ({id, ownerID}, data, origin) => { - send(mailbox, object([id, { - inbox: { - target: {id: id, ownerID: ownerID}, - timeStamp: Date.now(), - data: data, - origin: origin - } - }])); -}; -Source.prototype.postMessage = function(data, origin) { - Source.postMessage(this, data, origin); -}; - -const Message = function({type, data, source, origin, timeStamp}) { - this.type = type; - this.data = data; - this.origin = origin; - this.timeStamp = timeStamp; - this.source = new Source(source); -}; - - -const frames = new Map(); -const sources = new Map(); - -const Frame = Class({ - extends: EventTarget, - implements: [Disposable, Source], - initialize: function(params={}) { - const options = validate(params); - const id = makeID(options.name || options.url); - - if (frames.has(id)) - throw Error("Frame with this id already exists: " + id); - - const initial = { id: id, url: resolve(options.url) }; - this.id = id; - - setListeners(this, params); - - frames.set(this.id, this); - - send(output, object([id, initial])); - }, - get url() { - const state = reactor.value[this.id]; - return state && state.url; - }, - destroy: function() { - send(output, object([this.id, null])); - frames.delete(this.id); - off(this); - }, - // `JSON.stringify` serializes objects based of the return - // value of this method. For convinienc we provide this method - // to serialize actual state data. - toJSON: function() { - return { id: this.id, url: this.url }; - } -}); -identify.define(Frame, frame => frame.id); - -exports.Frame = Frame; - -const reactor = new Reactor({ - onStep: (present, past) => { - const delta = diff(past, present); - - each(([id, update]) => { - const frame = frames.get(id); - if (update) { - if (!past[id]) - emit(frame, "register"); - - if (update.outbox) - emit(frame, "message", new Message(present[id].outbox)); - - each(([ownerID, state]) => { - const readyState = state ? state.readyState : "detach"; - const type = readyState === "loading" ? "attach" : - readyState === "interactive" ? "ready" : - readyState === "complete" ? "load" : - readyState; - - // TODO: Cache `Source` instances somewhere to preserve - // identity. - emit(frame, type, {type: type, - source: new Source({id: id, ownerID: ownerID})}); - }, pairs(update.owners)); - } - }, pairs(delta)); - } -}); -reactor.run(input); diff --git a/addon-sdk/source/lib/sdk/ui/frame/view.html b/addon-sdk/source/lib/sdk/ui/frame/view.html deleted file mode 100644 index 2a405b583..000000000 --- a/addon-sdk/source/lib/sdk/ui/frame/view.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <script> - // HACK: This is not an ideal way to deliver chrome messages - // to an inner frame content but seems only way that would - // make `event.source` this (outer frame) window. - window.onmessage = function(event) { - var frame = document.querySelector("iframe"); - var content = frame.contentWindow; - // If message is posted from chrome it has no `event.source`. - if (event.source === null) - content.postMessage(event.data, "*"); - }; - </script> - </head> - <body style="overflow: hidden"></body> -</html> diff --git a/addon-sdk/source/lib/sdk/ui/frame/view.js b/addon-sdk/source/lib/sdk/ui/frame/view.js deleted file mode 100644 index 2eb4df2b7..000000000 --- a/addon-sdk/source/lib/sdk/ui/frame/view.js +++ /dev/null @@ -1,150 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -const { Cu, Ci } = require("chrome"); -const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); -const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils"); -const { InputPort } = require("../../input/system"); -const { OutputPort } = require("../../output/system"); -const { LastClosed } = require("../../input/browser"); -const { pairs, keys, object, each } = require("../../util/sequence"); -const { curry, compose } = require("../../lang/functional"); -const { getFrameElement, getOuterId, - getByOuterId, getOwnerBrowserWindow } = require("../../window/utils"); -const { patch, diff } = require("diffpatcher/index"); -const { encode } = require("../../base64"); -const { Frames } = require("../../input/frame"); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const HTML_NS = "http://www.w3.org/1999/xhtml"; -const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html"); - -const mailbox = new OutputPort({ id: "frame-mailbox" }); - -const frameID = frame => frame.id.replace("outer-", ""); -const windowID = compose(getOuterId, getOwnerBrowserWindow); - -const getOuterFrame = (windowID, frameID) => - getByOuterId(windowID).document.getElementById("outer-" + frameID); - -const listener = ({target, source, data, origin, timeStamp}) => { - // And sent received message to outbox so that frame API model - // will deal with it. - if (source && source !== target) { - const frame = getFrameElement(target); - const id = frameID(frame); - send(mailbox, object([id, { - outbox: {type: "message", - source: {id: id, ownerID: windowID(frame)}, - data: data, - origin: origin, - timeStamp: timeStamp}}])); - } -}; - -// Utility function used to create frame with a given `state` and -// inject it into given `window`. -const registerFrame = ({id, url}) => { - CustomizableUI.createWidget({ - id: id, - type: "custom", - removable: true, - onBuild: document => { - let view = document.createElementNS(XUL_NS, "toolbaritem"); - view.setAttribute("id", id); - view.setAttribute("flex", 2); - - let outerFrame = document.createElementNS(XUL_NS, "iframe"); - outerFrame.setAttribute("src", OUTER_FRAME_URI); - outerFrame.setAttribute("id", "outer-" + id); - outerFrame.setAttribute("data-is-sdk-outer-frame", true); - outerFrame.setAttribute("type", "content"); - outerFrame.setAttribute("transparent", true); - outerFrame.setAttribute("flex", 2); - outerFrame.setAttribute("style", "overflow: hidden;"); - outerFrame.setAttribute("scrolling", "no"); - outerFrame.setAttribute("disablehistory", true); - outerFrame.setAttribute("seamless", "seamless"); - outerFrame.addEventListener("load", function onload() { - outerFrame.removeEventListener("load", onload, true); - - let doc = outerFrame.contentDocument; - - let innerFrame = doc.createElementNS(HTML_NS, "iframe"); - innerFrame.setAttribute("id", id); - innerFrame.setAttribute("src", url); - innerFrame.setAttribute("seamless", "seamless"); - innerFrame.setAttribute("sandbox", "allow-scripts"); - innerFrame.setAttribute("scrolling", "no"); - innerFrame.setAttribute("data-is-sdk-inner-frame", true); - innerFrame.setAttribute("style", [ "border:none", - "position:absolute", "width:100%", "top: 0", - "left: 0", "overflow: hidden"].join(";")); - - doc.body.appendChild(innerFrame); - }, true); - - view.appendChild(outerFrame); - - return view; - } - }); -}; - -const unregisterFrame = CustomizableUI.destroyWidget; - -const deliverMessage = curry((frameID, data, windowID) => { - const frame = getOuterFrame(windowID, frameID); - const content = frame && frame.contentWindow; - - if (content) - content.postMessage(data, content.location.origin); -}); - -const updateFrame = (id, {inbox, owners}, present) => { - if (inbox) { - const { data, target:{ownerID}, source } = present[id].inbox; - if (ownerID) - deliverMessage(id, data, ownerID); - else - each(deliverMessage(id, data), keys(present[id].owners)); - } - - each(setupView(id), pairs(owners)); -}; - -const setupView = curry((frameID, [windowID, state]) => { - if (state && state.readyState === "loading") { - const frame = getOuterFrame(windowID, frameID); - // Setup a message listener on contentWindow. - frame.contentWindow.addEventListener("message", listener); - } -}); - - -const reactor = new Reactor({ - onStep: (present, past) => { - const delta = diff(past, present); - - // Apply frame changes - each(([id, update]) => { - if (update === null) - unregisterFrame(id); - else if (past[id]) - updateFrame(id, update, present); - else - registerFrame(update); - }, pairs(delta)); - }, - onEnd: state => each(unregisterFrame, keys(state)) -}); -reactor.run(Frames); diff --git a/addon-sdk/source/lib/sdk/ui/id.js b/addon-sdk/source/lib/sdk/ui/id.js deleted file mode 100644 index d17eb0a4e..000000000 --- a/addon-sdk/source/lib/sdk/ui/id.js +++ /dev/null @@ -1,27 +0,0 @@ -/* 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': 'experimental' -}; - -const method = require('../../method/core'); -const { uuid } = require('../util/uuid'); - -// NOTE: use lang/functional memoize when it is updated to use WeakMap -function memoize(f) { - const memo = new WeakMap(); - - return function memoizer(o) { - let key = o; - if (!memo.has(key)) - memo.set(key, f.apply(this, arguments)); - return memo.get(key); - }; -} - -var identify = method('identify'); -identify.define(Object, memoize(function() { return uuid(); })); -exports.identify = identify; diff --git a/addon-sdk/source/lib/sdk/ui/sidebar.js b/addon-sdk/source/lib/sdk/ui/sidebar.js deleted file mode 100644 index 59e35ea11..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar.js +++ /dev/null @@ -1,311 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '*' - } -}; - -const { Class } = require('../core/heritage'); -const { merge } = require('../util/object'); -const { Disposable } = require('../core/disposable'); -const { off, emit, setListeners } = require('../event/core'); -const { EventTarget } = require('../event/target'); -const { URL } = require('../url'); -const { add, remove, has, clear, iterator } = require('../lang/weak-set'); -const { id: addonID, data } = require('../self'); -const { WindowTracker } = require('../deprecated/window-utils'); -const { isShowing } = require('./sidebar/utils'); -const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils'); -const { ns } = require('../core/namespace'); -const { remove: removeFromArray } = require('../util/array'); -const { show, hide, toggle } = require('./sidebar/actions'); -const { Worker } = require('../deprecated/sync-worker'); -const { contract: sidebarContract } = require('./sidebar/contract'); -const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view'); -const { defer } = require('../core/promise'); -const { models, views, viewsFor, modelFor } = require('./sidebar/namespace'); -const { isLocalURL } = require('../url'); -const { ensure } = require('../system/unload'); -const { identify } = require('./id'); -const { uuid } = require('../util/uuid'); -const { viewFor } = require('../view/core'); - -const resolveURL = (url) => url ? data.url(url) : url; - -const sidebarNS = ns(); - -const WEB_PANEL_BROWSER_ID = 'web-panels-browser'; - -var sidebars = {}; - -const Sidebar = Class({ - implements: [ Disposable ], - extends: EventTarget, - setup: function(options) { - // inital validation for the model information - let model = sidebarContract(options); - - // save the model information - models.set(this, model); - - // generate an id if one was not provided - model.id = model.id || addonID + '-' + uuid(); - - // further validation for the title and url - validateTitleAndURLCombo({}, this.title, this.url); - - const self = this; - const internals = sidebarNS(self); - const windowNS = internals.windowNS = ns(); - - // see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148 - ensure(this, 'destroy'); - - setListeners(this, options); - - let bars = []; - internals.tracker = WindowTracker({ - onTrack: function(window) { - if (!isBrowser(window)) - return; - - let sidebar = window.document.getElementById('sidebar'); - let sidebarBox = window.document.getElementById('sidebar-box'); - - let bar = create(window, { - id: self.id, - title: self.title, - sidebarurl: self.url - }); - bars.push(bar); - windowNS(window).bar = bar; - - bar.addEventListener('command', function() { - if (isSidebarShowing(window, self)) { - hideSidebar(window, self).catch(() => {}); - return; - } - - showSidebar(window, self); - }, false); - - function onSidebarLoad() { - // check if the sidebar is ready - let isReady = sidebar.docShell && sidebar.contentDocument; - if (!isReady) - return; - - // check if it is a web panel - let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID); - if (!panelBrowser) { - bar.removeAttribute('checked'); - return; - } - - let sbTitle = window.document.getElementById('sidebar-title'); - function onWebPanelSidebarCreated() { - if (panelBrowser.contentWindow.location != resolveURL(model.url) || - sbTitle.value != model.title) { - return; - } - - let worker = windowNS(window).worker = Worker({ - window: panelBrowser.contentWindow, - injectInDocument: true - }); - - function onWebPanelSidebarUnload() { - windowNS(window).onWebPanelSidebarUnload = null; - - // uncheck the associated menuitem - bar.setAttribute('checked', 'false'); - - emit(self, 'hide', {}); - emit(self, 'detach', worker); - windowNS(window).worker = null; - } - windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload; - panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true); - - // check the associated menuitem - bar.setAttribute('checked', 'true'); - - function onWebPanelSidebarReady() { - panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady, false); - windowNS(window).onWebPanelSidebarReady = null; - - emit(self, 'ready', worker); - } - windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady; - panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady, false); - - function onWebPanelSidebarLoad() { - panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true); - windowNS(window).onWebPanelSidebarLoad = null; - - // TODO: decide if returning worker is acceptable.. - //emit(self, 'show', { worker: worker }); - emit(self, 'show', {}); - } - windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad; - panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true); - - emit(self, 'attach', worker); - } - windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated; - panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true); - } - windowNS(window).onSidebarLoad = onSidebarLoad; - sidebar.addEventListener('load', onSidebarLoad, true); // removed properly - }, - onUntrack: function(window) { - if (!isBrowser(window)) - return; - - // hide the sidebar if it is showing - hideSidebar(window, self).catch(() => {}); - - // kill the menu item - let { bar } = windowNS(window); - if (bar) { - removeFromArray(viewsFor(self), bar); - dispose(bar); - } - - // kill listeners - let sidebar = window.document.getElementById('sidebar'); - - if (windowNS(window).onSidebarLoad) { - sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true) - windowNS(window).onSidebarLoad = null; - } - - let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID); - if (windowNS(window).onWebPanelSidebarCreated) { - panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true); - windowNS(window).onWebPanelSidebarCreated = null; - } - - if (windowNS(window).onWebPanelSidebarReady) { - panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady, false); - windowNS(window).onWebPanelSidebarReady = null; - } - - if (windowNS(window).onWebPanelSidebarLoad) { - panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true); - windowNS(window).onWebPanelSidebarLoad = null; - } - - if (windowNS(window).onWebPanelSidebarUnload) { - panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true); - windowNS(window).onWebPanelSidebarUnload(); - } - } - }); - - views.set(this, bars); - - add(sidebars, this); - }, - get id() { - return (modelFor(this) || {}).id; - }, - get title() { - return (modelFor(this) || {}).title; - }, - set title(v) { - // destroyed? - if (!modelFor(this)) - return; - // validation - if (typeof v != 'string') - throw Error('title must be a string'); - validateTitleAndURLCombo(this, v, this.url); - // do update - updateTitle(this, v); - return modelFor(this).title = v; - }, - get url() { - return (modelFor(this) || {}).url; - }, - set url(v) { - // destroyed? - if (!modelFor(this)) - return; - - // validation - if (!isLocalURL(v)) - throw Error('the url must be a valid local url'); - - validateTitleAndURLCombo(this, this.title, v); - - // do update - updateURL(this, v); - modelFor(this).url = v; - }, - show: function(window) { - return showSidebar(viewFor(window), this); - }, - hide: function(window) { - return hideSidebar(viewFor(window), this); - }, - dispose: function() { - const internals = sidebarNS(this); - - off(this); - - remove(sidebars, this); - - // stop tracking windows - if (internals.tracker) { - internals.tracker.unload(); - } - - internals.tracker = null; - internals.windowNS = null; - - views.delete(this); - models.delete(this); - } -}); -exports.Sidebar = Sidebar; - -function validateTitleAndURLCombo(sidebar, title, url) { - url = resolveURL(url); - - if (sidebar.title == title && sidebar.url == url) { - return false; - } - - for (let window of windows(null, { includePrivate: true })) { - let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]'); - if (sidebar) { - throw Error('The provided title and url combination is invalid (already used).'); - } - } - - return false; -} - -isShowing.define(Sidebar, isSidebarShowing.bind(null, null)); -show.define(Sidebar, showSidebar.bind(null, null)); -hide.define(Sidebar, hideSidebar.bind(null, null)); - -identify.define(Sidebar, function(sidebar) { - return sidebar.id; -}); - -function toggleSidebar(window, sidebar) { - // TODO: make sure this is not private - window = window || getMostRecentBrowserWindow(); - if (isSidebarShowing(window, sidebar)) { - return hideSidebar(window, sidebar); - } - return showSidebar(window, sidebar); -} -toggle.define(Sidebar, toggleSidebar.bind(null, null)); diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/actions.js b/addon-sdk/source/lib/sdk/ui/sidebar/actions.js deleted file mode 100644 index 4a52984c9..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar/actions.js +++ /dev/null @@ -1,10 +0,0 @@ -/* 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 method = require('../../../method/core'); - -exports.show = method('show'); -exports.hide = method('hide'); -exports.toggle = method('toggle'); diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/contract.js b/addon-sdk/source/lib/sdk/ui/sidebar/contract.js deleted file mode 100644 index b59c37c0b..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar/contract.js +++ /dev/null @@ -1,27 +0,0 @@ -/* 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 { contract } = require('../../util/contract'); -const { isValidURI, URL, isLocalURL } = require('../../url'); -const { isNil, isObject, isString } = require('../../lang/type'); - -exports.contract = contract({ - id: { - is: [ 'string', 'undefined' ], - ok: v => /^[a-z0-9-_]+$/i.test(v), - msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' + - 'underscores are allowed).' - }, - title: { - is: [ 'string' ], - ok: v => v.length - }, - url: { - is: [ 'string' ], - ok: v => isLocalURL(v), - map: v => v.toString(), - msg: 'The option "url" must be a valid local URI.' - } -}); diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js b/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js deleted file mode 100644 index d79725d1a..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar/namespace.js +++ /dev/null @@ -1,15 +0,0 @@ -/* 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 models = exports.models = new WeakMap(); -const views = exports.views = new WeakMap(); -exports.buttons = new WeakMap(); - -exports.viewsFor = function viewsFor(sidebar) { - return views.get(sidebar); -}; -exports.modelFor = function modelFor(sidebar) { - return models.get(sidebar); -}; diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/utils.js b/addon-sdk/source/lib/sdk/ui/sidebar/utils.js deleted file mode 100644 index d6145c32e..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -/* 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 method = require('../../../method/core'); - -exports.isShowing = method('isShowing'); diff --git a/addon-sdk/source/lib/sdk/ui/sidebar/view.js b/addon-sdk/source/lib/sdk/ui/sidebar/view.js deleted file mode 100644 index c91e69d3d..000000000 --- a/addon-sdk/source/lib/sdk/ui/sidebar/view.js +++ /dev/null @@ -1,214 +0,0 @@ -/* 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', - 'engines': { - 'Firefox': '*' - } -}; - -const { models, buttons, views, viewsFor, modelFor } = require('./namespace'); -const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../../window/utils'); -const { setStateFor } = require('../state'); -const { defer } = require('../../core/promise'); -const { isPrivateBrowsingSupported, data } = require('../../self'); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const WEB_PANEL_BROWSER_ID = 'web-panels-browser'; - -const resolveURL = (url) => url ? data.url(url) : url; - -function create(window, details) { - let id = makeID(details.id); - let { document } = window; - - if (document.getElementById(id)) - throw new Error('The ID "' + details.id + '" seems already used.'); - - let menuitem = document.createElementNS(XUL_NS, 'menuitem'); - menuitem.setAttribute('id', id); - menuitem.setAttribute('label', details.title); - menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl)); - menuitem.setAttribute('checked', 'false'); - menuitem.setAttribute('type', 'checkbox'); - menuitem.setAttribute('group', 'sidebar'); - menuitem.setAttribute('autoCheck', 'false'); - - document.getElementById('viewSidebarMenu').appendChild(menuitem); - - return menuitem; -} -exports.create = create; - -function dispose(menuitem) { - menuitem.parentNode.removeChild(menuitem); -} -exports.dispose = dispose; - -function updateTitle(sidebar, title) { - let button = buttons.get(sidebar); - - for (let window of windows(null, { includePrivate: true })) { - let { document } = window; - - // update the button - if (button) { - setStateFor(button, window, { label: title }); - } - - // update the menuitem - let mi = document.getElementById(makeID(sidebar.id)); - if (mi) { - mi.setAttribute('label', title) - } - - // update sidebar, if showing - if (isSidebarShowing(window, sidebar)) { - document.getElementById('sidebar-title').setAttribute('value', title); - } - } -} -exports.updateTitle = updateTitle; - -function updateURL(sidebar, url) { - let eleID = makeID(sidebar.id); - - url = resolveURL(url); - - for (let window of windows(null, { includePrivate: true })) { - // update the menuitem - let mi = window.document.getElementById(eleID); - if (mi) { - mi.setAttribute('sidebarurl', url) - } - - // update sidebar, if showing - if (isSidebarShowing(window, sidebar)) { - showSidebar(window, sidebar, url); - } - } -} -exports.updateURL = updateURL; - -function isSidebarShowing(window, sidebar) { - let win = window || getMostRecentBrowserWindow(); - - // make sure there is a window - if (!win) { - return false; - } - - // make sure there is a sidebar for the window - let sb = win.document.getElementById('sidebar'); - let sidebarTitle = win.document.getElementById('sidebar-title'); - if (!(sb && sidebarTitle)) { - return false; - } - - // checks if the sidebar box is hidden - let sbb = win.document.getElementById('sidebar-box'); - if (!sbb || sbb.hidden) { - return false; - } - - if (sidebarTitle.value == modelFor(sidebar).title) { - let url = resolveURL(modelFor(sidebar).url); - - // checks if the sidebar is loading - if (win.gWebPanelURI == url) { - return true; - } - - // checks if the sidebar loaded already - let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID); - if (!ele) { - return false; - } - - if (ele.getAttribute('cachedurl') == url) { - return true; - } - - if (ele && ele.contentWindow && ele.contentWindow.location == url) { - return true; - } - } - - // default - return false; -} -exports.isSidebarShowing = isSidebarShowing; - -function showSidebar(window, sidebar, newURL) { - window = window || getMostRecentBrowserWindow(); - - let { promise, resolve, reject } = defer(); - let model = modelFor(sidebar); - - if (!newURL && isSidebarShowing(window, sidebar)) { - resolve({}); - } - else if (!isPrivateBrowsingSupported && isWindowPrivate(window)) { - reject(Error('You cannot show a sidebar on private windows')); - } - else { - sidebar.once('show', resolve); - - let menuitem = window.document.getElementById(makeID(model.id)); - menuitem.setAttribute('checked', true); - - window.openWebPanel(model.title, resolveURL(newURL || model.url)); - } - - return promise; -} -exports.showSidebar = showSidebar; - - -function hideSidebar(window, sidebar) { - window = window || getMostRecentBrowserWindow(); - - let { promise, resolve, reject } = defer(); - - if (!isSidebarShowing(window, sidebar)) { - reject(Error('The sidebar is already hidden')); - } - else { - sidebar.once('hide', resolve); - - // Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775 - // the code for window.todggleSideBar().. - let { document } = window; - let sidebarEle = document.getElementById('sidebar'); - let sidebarTitle = document.getElementById('sidebar-title'); - let sidebarBox = document.getElementById('sidebar-box'); - let sidebarSplitter = document.getElementById('sidebar-splitter'); - let commandID = sidebarBox.getAttribute('sidebarcommand'); - let sidebarBroadcaster = document.getElementById(commandID); - - sidebarBox.hidden = true; - sidebarSplitter.hidden = true; - - sidebarEle.setAttribute('src', 'about:blank'); - //sidebarEle.docShell.createAboutBlankContentViewer(null); - - sidebarBroadcaster.removeAttribute('checked'); - sidebarBox.setAttribute('sidebarcommand', ''); - sidebarTitle.value = ''; - sidebarBox.hidden = true; - sidebarSplitter.hidden = true; - - // TODO: perhaps this isn't necessary if the window is not most recent? - window.gBrowser.selectedBrowser.focus(); - } - - return promise; -} -exports.hideSidebar = hideSidebar; - -function makeID(id) { - return 'jetpack-sidebar-' + id; -} diff --git a/addon-sdk/source/lib/sdk/ui/state.js b/addon-sdk/source/lib/sdk/ui/state.js deleted file mode 100644 index 152ce696d..000000000 --- a/addon-sdk/source/lib/sdk/ui/state.js +++ /dev/null @@ -1,239 +0,0 @@ -/* 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'; - -// The Button module currently supports only Firefox. -// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps -module.metadata = { - 'stability': 'experimental', - 'engines': { - 'Firefox': '*', - 'SeaMonkey': '*', - 'Thunderbird': '*' - } -}; - -const { Ci } = require('chrome'); - -const events = require('../event/utils'); -const { events: browserEvents } = require('../browser/events'); -const { events: tabEvents } = require('../tab/events'); -const { events: stateEvents } = require('./state/events'); - -const { windows, isInteractive, getFocusedBrowser } = require('../window/utils'); -const { getActiveTab, getOwnerWindow } = require('../tabs/utils'); - -const { ignoreWindow } = require('../private-browsing/utils'); - -const { freeze } = Object; -const { merge } = require('../util/object'); -const { on, off, emit } = require('../event/core'); - -const { add, remove, has, clear, iterator } = require('../lang/weak-set'); -const { isNil } = require('../lang/type'); - -const { viewFor } = require('../view/core'); - -const components = new WeakMap(); - -const ERR_UNREGISTERED = 'The state cannot be set or get. ' + - 'The object may be not be registered, or may already have been unloaded.'; - -const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' + - 'Only window, tab and registered component are valid targets.'; - -const isWindow = thing => thing instanceof Ci.nsIDOMWindow; -const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab'; -const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing)); -const isEnumerable = window => !ignoreWindow(window); -const browsers = _ => - windows('navigator:browser', { includePrivate: true }).filter(isInteractive); -const getMostRecentTab = _ => getActiveTab(getFocusedBrowser()); - -function getStateFor(component, target) { - if (!isRegistered(component)) - throw new Error(ERR_UNREGISTERED); - - if (!components.has(component)) - return null; - - let states = components.get(component); - - if (target) { - if (isTab(target) || isWindow(target) || target === component) - return states.get(target) || null; - else - throw new Error(ERR_INVALID_TARGET); - } - - return null; -} -exports.getStateFor = getStateFor; - -function getDerivedStateFor(component, target) { - if (!isRegistered(component)) - throw new Error(ERR_UNREGISTERED); - - if (!components.has(component)) - return null; - - let states = components.get(component); - - let componentState = states.get(component); - let windowState = null; - let tabState = null; - - if (target) { - // has a target - if (isTab(target)) { - windowState = states.get(getOwnerWindow(target), null); - - if (states.has(target)) { - // we have a tab state - tabState = states.get(target); - } - } - else if (isWindow(target) && states.has(target)) { - // we have a window state - windowState = states.get(target); - } - } - - return freeze(merge({}, componentState, windowState, tabState)); -} -exports.getDerivedStateFor = getDerivedStateFor; - -function setStateFor(component, target, state) { - if (!isRegistered(component)) - throw new Error(ERR_UNREGISTERED); - - let isComponentState = target === component; - let targetWindows = isWindow(target) ? [target] : - isActiveTab(target) ? [getOwnerWindow(target)] : - isComponentState ? browsers() : - isTab(target) ? [] : - null; - - if (!targetWindows) - throw new Error(ERR_INVALID_TARGET); - - // initialize the state's map - if (!components.has(component)) - components.set(component, new WeakMap()); - - let states = components.get(component); - - if (state === null && !isComponentState) // component state can't be deleted - states.delete(target); - else { - let base = isComponentState ? states.get(target) : null; - states.set(target, freeze(merge({}, base, state))); - } - - render(component, targetWindows); -} -exports.setStateFor = setStateFor; - -function render(component, targetWindows) { - targetWindows = targetWindows ? [].concat(targetWindows) : browsers(); - - for (let window of targetWindows.filter(isEnumerable)) { - let tabState = getDerivedStateFor(component, getActiveTab(window)); - - emit(stateEvents, 'data', { - type: 'render', - target: component, - window: window, - state: tabState - }); - - } -} -exports.render = render; - -function properties(contract) { - let { rules } = contract; - let descriptor = Object.keys(rules).reduce(function(descriptor, name) { - descriptor[name] = { - get: function() { return getDerivedStateFor(this)[name] }, - set: function(value) { - let changed = {}; - changed[name] = value; - - setStateFor(this, this, contract(changed)); - } - } - return descriptor; - }, {}); - - return Object.create(Object.prototype, descriptor); -} -exports.properties = properties; - -function state(contract) { - return { - state: function state(target, state) { - let nativeTarget = target === 'window' ? getFocusedBrowser() - : target === 'tab' ? getMostRecentTab() - : target === this ? null - : viewFor(target); - - if (!nativeTarget && target !== this && !isNil(target)) - throw new Error(ERR_INVALID_TARGET); - - target = nativeTarget || target; - - // jquery style - return arguments.length < 2 - ? getDerivedStateFor(this, target) - : setStateFor(this, target, contract(state)) - } - } -} -exports.state = state; - -const register = (component, state) => { - add(components, component); - setStateFor(component, component, state); -} -exports.register = register; - -const unregister = component => { - remove(components, component); -} -exports.unregister = unregister; - -const isRegistered = component => has(components, component); -exports.isRegistered = isRegistered; - -var tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect'); -var tabClose = events.filter(tabEvents, e => e.type === 'TabClose'); -var windowOpen = events.filter(browserEvents, e => e.type === 'load'); -var windowClose = events.filter(browserEvents, e => e.type === 'close'); - -var close = events.merge([tabClose, windowClose]); -var activate = events.merge([windowOpen, tabSelect]); - -on(activate, 'data', ({target}) => { - let [window, tab] = isWindow(target) - ? [target, getActiveTab(target)] - : [getOwnerWindow(target), target]; - - if (ignoreWindow(window)) return; - - for (let component of iterator(components)) { - emit(stateEvents, 'data', { - type: 'render', - target: component, - window: window, - state: getDerivedStateFor(component, tab) - }); - } -}); - -on(close, 'data', function({target}) { - for (let component of iterator(components)) { - components.get(component).delete(target); - } -}); diff --git a/addon-sdk/source/lib/sdk/ui/state/events.js b/addon-sdk/source/lib/sdk/ui/state/events.js deleted file mode 100644 index 98909656a..000000000 --- a/addon-sdk/source/lib/sdk/ui/state/events.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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': 'experimental', - 'engines': { - 'Firefox': '*', - 'SeaMonkey': '*', - 'Thunderbird': '*' - } -}; - -var channel = {}; - -exports.events = channel; diff --git a/addon-sdk/source/lib/sdk/ui/toolbar.js b/addon-sdk/source/lib/sdk/ui/toolbar.js deleted file mode 100644 index c1becab2d..000000000 --- a/addon-sdk/source/lib/sdk/ui/toolbar.js +++ /dev/null @@ -1,16 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -const { Toolbar } = require("./toolbar/model"); -require("./toolbar/view"); - -exports.Toolbar = Toolbar; diff --git a/addon-sdk/source/lib/sdk/ui/toolbar/model.js b/addon-sdk/source/lib/sdk/ui/toolbar/model.js deleted file mode 100644 index 5c5428606..000000000 --- a/addon-sdk/source/lib/sdk/ui/toolbar/model.js +++ /dev/null @@ -1,151 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -const { Class } = require("../../core/heritage"); -const { EventTarget } = require("../../event/target"); -const { off, setListeners, emit } = require("../../event/core"); -const { Reactor, foldp, merges, send } = require("../../event/utils"); -const { Disposable } = require("../../core/disposable"); -const { InputPort } = require("../../input/system"); -const { OutputPort } = require("../../output/system"); -const { identify } = require("../id"); -const { pairs, object, map, each } = require("../../util/sequence"); -const { patch, diff } = require("diffpatcher/index"); -const { contract } = require("../../util/contract"); -const { id: addonID } = require("../../self"); - -// Input state is accumulated from the input received form the toolbar -// view code & local output. Merging local output reflects local state -// changes without complete roundloop. -const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" })); -const output = new OutputPort({ id: "toolbar-change" }); - -// Takes toolbar title and normalizes is to an -// identifier, also prefixes with add-on id. -const titleToId = title => - ("toolbar-" + addonID + "-" + title). - toLowerCase(). - replace(/\s/g, "-"). - replace(/[^A-Za-z0-9_\-]/g, ""); - -const validate = contract({ - title: { - is: ["string"], - ok: x => x.length > 0, - msg: "The `option.title` string must be provided" - }, - items: { - is:["undefined", "object", "array"], - msg: "The `options.items` must be iterable sequence of items" - }, - hidden: { - is: ["boolean", "undefined"], - msg: "The `options.hidden` must be boolean" - } -}); - -// Toolbars is a mapping between `toolbar.id` & `toolbar` instances, -// which is used to find intstance for dispatching events. -var toolbars = new Map(); - -const Toolbar = Class({ - extends: EventTarget, - implements: [Disposable], - initialize: function(params={}) { - const options = validate(params); - const id = titleToId(options.title); - - if (toolbars.has(id)) - throw Error("Toolbar with this id already exists: " + id); - - // Set of the items in the toolbar isn't mutable, as a matter of fact - // it just defines desired set of items, actual set is under users - // control. Conver test to an array and freeze to make sure users won't - // try mess with it. - const items = Object.freeze(options.items ? [...options.items] : []); - - const initial = { - id: id, - title: options.title, - // By default toolbars are visible when add-on is installed, unless - // add-on authors decides it should be hidden. From that point on - // user is in control. - collapsed: !!options.hidden, - // In terms of state only identifiers of items matter. - items: items.map(identify) - }; - - this.id = id; - this.items = items; - - toolbars.set(id, this); - setListeners(this, params); - - // Send initial state to the host so it can reflect it - // into a user interface. - send(output, object([id, initial])); - }, - - get title() { - const state = reactor.value[this.id]; - return state && state.title; - }, - get hidden() { - const state = reactor.value[this.id]; - return state && state.collapsed; - }, - - destroy: function() { - send(output, object([this.id, null])); - }, - // `JSON.stringify` serializes objects based of the return - // value of this method. For convinienc we provide this method - // to serialize actual state data. Note: items will also be - // serialized so they should probably implement `toJSON`. - toJSON: function() { - return { - id: this.id, - title: this.title, - hidden: this.hidden, - items: this.items - }; - } -}); -exports.Toolbar = Toolbar; -identify.define(Toolbar, toolbar => toolbar.id); - -const dispose = toolbar => { - toolbars.delete(toolbar.id); - emit(toolbar, "detach"); - off(toolbar); -}; - -const reactor = new Reactor({ - onStep: (present, past) => { - const delta = diff(past, present); - - each(([id, update]) => { - const toolbar = toolbars.get(id); - - // Remove - if (!update) - dispose(toolbar); - // Add - else if (!past[id]) - emit(toolbar, "attach"); - // Update - else - emit(toolbar, update.collapsed ? "hide" : "show", toolbar); - }, pairs(delta)); - } -}); -reactor.run(input); diff --git a/addon-sdk/source/lib/sdk/ui/toolbar/view.js b/addon-sdk/source/lib/sdk/ui/toolbar/view.js deleted file mode 100644 index 4ef0c3d46..000000000 --- a/addon-sdk/source/lib/sdk/ui/toolbar/view.js +++ /dev/null @@ -1,248 +0,0 @@ -/* 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": "experimental", - "engines": { - "Firefox": "> 28" - } -}; - -const { Cu } = require("chrome"); -const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); -const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils"); -const { InputPort } = require("../../input/system"); -const { OutputPort } = require("../../output/system"); -const { Interactive } = require("../../input/browser"); -const { CustomizationInput } = require("../../input/customizable-ui"); -const { pairs, map, isEmpty, object, - each, keys, values } = require("../../util/sequence"); -const { curry, flip } = require("../../lang/functional"); -const { patch, diff } = require("diffpatcher/index"); -const prefs = require("../../preferences/service"); -const { getByOuterId } = require("../../window/utils"); -const { ignoreWindow } = require('../../private-browsing/utils'); - -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; -const PREF_ROOT = "extensions.sdk-toolbar-collapsed."; - - -// There are two output ports one for publishing changes that occured -// and the other for change requests. Later is synchronous and is only -// consumed here. Note: it needs to be synchronous to avoid race conditions -// when `collapsed` attribute changes are caused by user interaction and -// toolbar is destroyed between the ticks. -const output = new OutputPort({ id: "toolbar-changed" }); -const syncoutput = new OutputPort({ id: "toolbar-change", sync: true }); - -// Merge disptached changes and recevied changes from models to keep state up to -// date. -const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }), - new InputPort({ id: "toolbar-change" })])); -const State = lift((toolbars, windows, customizable) => - ({windows: windows, toolbars: toolbars, customizable: customizable}), - Toolbars, Interactive, new CustomizationInput()); - -// Shared event handler that makes `event.target.parent` collapsed. -// Used as toolbar's close buttons click handler. -const collapseToolbar = event => { - const toolbar = event.target.parentNode; - toolbar.collapsed = true; -}; - -const parseAttribute = x => - x === "true" ? true : - x === "false" ? false : - x === "" ? null : - x; - -// Shared mutation observer that is used to observe `toolbar` node's -// attribute mutations. Mutations are aggregated in the `delta` hash -// and send to `ToolbarStateChanged` channel to let model know state -// has changed. -const attributesChanged = mutations => { - const delta = mutations.reduce((changes, {attributeName, target}) => { - const id = target.id; - const field = attributeName === "toolbarname" ? "title" : attributeName; - let change = changes[id] || (changes[id] = {}); - change[field] = parseAttribute(target.getAttribute(attributeName)); - return changes; - }, {}); - - // Calculate what are the updates from the current state and if there are - // any send them. - const updates = diff(reactor.value, patch(reactor.value, delta)); - - if (!isEmpty(pairs(updates))) { - // TODO: Consider sending sync to make sure that there won't be a new - // update doing a delete in the meantime. - send(syncoutput, updates); - } -}; - - -// Utility function creates `toolbar` with a "close" button and returns -// it back. In addition it set's up a listener and observer to communicate -// state changes. -const addView = curry((options, {document, window}) => { - if (ignoreWindow(window)) - return; - - let view = document.createElementNS(XUL_NS, "toolbar"); - view.setAttribute("id", options.id); - view.setAttribute("collapsed", options.collapsed); - view.setAttribute("toolbarname", options.title); - view.setAttribute("pack", "end"); - view.setAttribute("customizable", "false"); - view.setAttribute("style", "padding: 2px 0; max-height: 40px;"); - view.setAttribute("mode", "icons"); - view.setAttribute("iconsize", "small"); - view.setAttribute("context", "toolbar-context-menu"); - view.setAttribute("class", "chromeclass-toolbar"); - - let label = document.createElementNS(XUL_NS, "label"); - label.setAttribute("value", options.title); - label.setAttribute("collapsed", "true"); - view.appendChild(label); - - let closeButton = document.createElementNS(XUL_NS, "toolbarbutton"); - closeButton.setAttribute("id", "close-" + options.id); - closeButton.setAttribute("class", "close-icon"); - closeButton.setAttribute("customizable", false); - closeButton.addEventListener("command", collapseToolbar); - - view.appendChild(closeButton); - - // In order to have a close button not costumizable, aligned on the right, - // leaving the customizable capabilities of Australis, we need to create - // a toolbar inside a toolbar. - // This is should be a temporary hack, we should have a proper XBL for toolbar - // instead. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=982005 - let toolbar = document.createElementNS(XUL_NS, "toolbar"); - toolbar.setAttribute("id", "inner-" + options.id); - toolbar.setAttribute("defaultset", options.items.join(",")); - toolbar.setAttribute("customizable", "true"); - toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden"); - toolbar.setAttribute("mode", "icons"); - toolbar.setAttribute("iconsize", "small"); - toolbar.setAttribute("context", "toolbar-context-menu"); - toolbar.setAttribute("flex", "1"); - - view.insertBefore(toolbar, closeButton); - - const observer = new document.defaultView.MutationObserver(attributesChanged); - observer.observe(view, { attributes: true, - attributeFilter: ["collapsed", "toolbarname"] }); - - const toolbox = document.getElementById("navigator-toolbox"); - toolbox.appendChild(view); -}); -const viewAdd = curry(flip(addView)); - -const removeView = curry((id, {document}) => { - const view = document.getElementById(id); - if (view) view.remove(); -}); - -const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => { - const view = document.getElementById(id); - - if (!view) - return; - - if (title) - view.setAttribute("toolbarname", title); - - if (collapsed !== void(0)) - view.setAttribute("collapsed", Boolean(collapsed)); - - if (isCustomizing !== void(0)) { - view.querySelector("label").collapsed = !isCustomizing; - view.querySelector("toolbar").style.visibility = isCustomizing - ? "hidden" : "visible"; - } -}); - -const viewUpdate = curry(flip(updateView)); - -// Utility function used to register toolbar into CustomizableUI. -const registerToolbar = state => { - // If it's first additon register toolbar as customizableUI component. - CustomizableUI.registerArea("inner-" + state.id, { - type: CustomizableUI.TYPE_TOOLBAR, - legacy: true, - defaultPlacements: [...state.items] - }); -}; -// Utility function used to unregister toolbar from the CustomizableUI. -const unregisterToolbar = CustomizableUI.unregisterArea; - -const reactor = new Reactor({ - onStep: (present, past) => { - const delta = diff(past, present); - - each(([id, update]) => { - // If update is `null` toolbar is removed, in such case - // we unregister toolbar and remove it from each window - // it was added to. - if (update === null) { - unregisterToolbar("inner-" + id); - each(removeView(id), values(past.windows)); - - send(output, object([id, null])); - } - else if (past.toolbars[id]) { - // If `collapsed` state for toolbar was updated, persist - // it for a future sessions. - if (update.collapsed !== void(0)) - prefs.set(PREF_ROOT + id, update.collapsed); - - // Reflect update in each window it was added to. - each(updateView(id, update), values(past.windows)); - - send(output, object([id, update])); - } - // Hack: Mutation observers are invoked async, which means that if - // client does `hide(toolbar)` & then `toolbar.destroy()` by the - // time we'll get update for `collapsed` toolbar will be removed. - // For now we check if `update.id` is present which will be undefined - // in such cases. - else if (update.id) { - // If it is a new toolbar we create initial state by overriding - // `collapsed` filed with value persisted in previous sessions. - const state = patch(update, { - collapsed: prefs.get(PREF_ROOT + id, update.collapsed), - }); - - // Register toolbar and add it each window known in the past - // (note that new windows if any will be handled in loop below). - registerToolbar(state); - each(addView(state), values(past.windows)); - - send(output, object([state.id, state])); - } - }, pairs(delta.toolbars)); - - // Add views to every window that was added. - each(window => { - if (window) - each(viewAdd(window), values(past.toolbars)); - }, values(delta.windows)); - - each(([id, isCustomizing]) => { - each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}), - keys(present.toolbars)); - - }, pairs(delta.customizable)) - }, - onEnd: state => { - each(id => { - unregisterToolbar("inner-" + id); - each(removeView(id), values(state.windows)); - }, keys(state.toolbars)); - } -}); -reactor.run(State); diff --git a/addon-sdk/source/lib/sdk/uri/resource.js b/addon-sdk/source/lib/sdk/uri/resource.js deleted file mode 100644 index 8a1dcbf2c..000000000 --- a/addon-sdk/source/lib/sdk/uri/resource.js +++ /dev/null @@ -1,37 +0,0 @@ -/* 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 {Cc, Ci} = require("chrome"); -const ioService = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); -const resourceHandler = ioService.getProtocolHandler("resource"). - QueryInterface(Ci.nsIResProtocolHandler); - -const URI = (uri, base=null) => - ioService.newURI(uri, null, base && URI(base)) - -const mount = (domain, uri) => - resourceHandler.setSubstitution(domain, ioService.newURI(uri, null, null)); -exports.mount = mount; - -const unmount = (domain, uri) => - resourceHandler.setSubstitution(domain, null); -exports.unmount = unmount; - -const domain = 1; -const path = 2; -const resolve = (uri) => { - const match = /resource\:\/\/([^\/]+)\/{0,1}([\s\S]*)/.exec(uri); - const domain = match && match[1]; - const path = match && match[2]; - return !match ? null : - !resourceHandler.hasSubstitution(domain) ? null : - resourceHandler.resolveURI(URI(`/${path}`, `resource://${domain}/`)); -} -exports.resolve = resolve; diff --git a/addon-sdk/source/lib/sdk/url.js b/addon-sdk/source/lib/sdk/url.js deleted file mode 100644 index ae16ac4a8..000000000 --- a/addon-sdk/source/lib/sdk/url.js +++ /dev/null @@ -1,349 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, Cr, Cu } = require("chrome"); - -const { Class } = require("./core/heritage"); -const base64 = require("./base64"); -var tlds = Cc["@mozilla.org/network/effective-tld-service;1"] - .getService(Ci.nsIEffectiveTLDService); - -var ios = Cc['@mozilla.org/network/io-service;1'] - .getService(Ci.nsIIOService); - -var resProt = ios.getProtocolHandler("resource") - .QueryInterface(Ci.nsIResProtocolHandler); - -var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"] - .getService(Ci.nsIURLParser); - -const { Services } = Cu.import("resource://gre/modules/Services.jsm"); - -function newURI(uriStr, base) { - try { - let baseURI = base ? ios.newURI(base, null, null) : null; - return ios.newURI(uriStr, null, baseURI); - } - catch (e) { - if (e.result == Cr.NS_ERROR_MALFORMED_URI) { - throw new Error("malformed URI: " + uriStr); - } - if (e.result == Cr.NS_ERROR_FAILURE || - e.result == Cr.NS_ERROR_ILLEGAL_VALUE) { - throw new Error("invalid URI: " + uriStr); - } - } -} - -function resolveResourceURI(uri) { - var resolved; - try { - resolved = resProt.resolveURI(uri); - } - catch (e) { - if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) { - throw new Error("resource does not exist: " + uri.spec); - } - } - return resolved; -} - -var fromFilename = exports.fromFilename = function fromFilename(path) { - var file = Cc['@mozilla.org/file/local;1'] - .createInstance(Ci.nsILocalFile); - file.initWithPath(path); - return ios.newFileURI(file).spec; -}; - -var toFilename = exports.toFilename = function toFilename(url) { - var uri = newURI(url); - if (uri.scheme == "resource") - uri = newURI(resolveResourceURI(uri)); - if (uri.scheme == "chrome") { - var channel = ios.newChannelFromURI2(uri, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); - try { - channel = channel.QueryInterface(Ci.nsIFileChannel); - return channel.file.path; - } - catch (e) { - if (e.result == Cr.NS_NOINTERFACE) { - throw new Error("chrome url isn't on filesystem: " + url); - } - } - } - if (uri.scheme == "file") { - var file = uri.QueryInterface(Ci.nsIFileURL).file; - return file.path; - } - throw new Error("cannot map to filename: " + url); -}; - -function URL(url, base) { - if (!(this instanceof URL)) { - return new URL(url, base); - } - - var uri = newURI(url, base); - - var userPass = null; - try { - userPass = uri.userPass ? uri.userPass : null; - } - catch (e) { - if (e.result != Cr.NS_ERROR_FAILURE) { - throw e; - } - } - - var host = null; - try { - host = uri.host; - } - catch (e) { - if (e.result != Cr.NS_ERROR_FAILURE) { - throw e; - } - } - - var port = null; - try { - port = uri.port == -1 ? null : uri.port; - } - catch (e) { - if (e.result != Cr.NS_ERROR_FAILURE) { - throw e; - } - } - - let fileName = "/"; - try { - fileName = uri.QueryInterface(Ci.nsIURL).fileName; - } catch (e) { - if (e.result != Cr.NS_NOINTERFACE) { - throw e; - } - } - - let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}]; - URLParser.parsePath.apply(URLParser, uriData); - let [{ value: filepathPos }, { value: filepathLen }, - { value: queryPos }, { value: queryLen }, - { value: refPos }, { value: refLen }] = uriData.slice(2); - - let hash = uri.ref ? "#" + uri.ref : ""; - let pathname = uri.path.substr(filepathPos, filepathLen); - let search = uri.path.substr(queryPos, queryLen); - search = search ? "?" + search : ""; - - this.__defineGetter__("fileName", () => fileName); - this.__defineGetter__("scheme", () => uri.scheme); - this.__defineGetter__("userPass", () => userPass); - this.__defineGetter__("host", () => host); - this.__defineGetter__("hostname", () => host); - this.__defineGetter__("port", () => port); - this.__defineGetter__("path", () => uri.path); - this.__defineGetter__("pathname", () => pathname); - this.__defineGetter__("hash", () => hash); - this.__defineGetter__("href", () => uri.spec); - this.__defineGetter__("origin", () => uri.prePath); - this.__defineGetter__("protocol", () => uri.scheme + ":"); - this.__defineGetter__("search", () => search); - - Object.defineProperties(this, { - toString: { - value() { - return new String(uri.spec).toString(); - }, - enumerable: false - }, - valueOf: { - value() { - return new String(uri.spec).valueOf(); - }, - enumerable: false - }, - toSource: { - value() { - return new String(uri.spec).toSource(); - }, - enumerable: false - }, - // makes more sense to flatten to string, easier to travel across JSON - toJSON: { - value() { - return new String(uri.spec).toString(); - }, - enumerable: false - } - }); - - return this; -}; - -URL.prototype = Object.create(String.prototype); -exports.URL = URL; - -/** - * Parse and serialize a Data URL. - * - * See: http://tools.ietf.org/html/rfc2397 - * - * Note: Could be extended in the future to decode / encode automatically binary - * data. - */ -const DataURL = Class({ - - get base64 () { - return "base64" in this.parameters; - }, - - set base64 (value) { - if (value) - this.parameters["base64"] = ""; - else - delete this.parameters["base64"]; - }, - /** - * Initialize the Data URL object. If a uri is given, it will be parsed. - * - * @param {String} [uri] The uri to parse - * - * @throws {URIError} if the Data URL is malformed - */ - initialize: function(uri) { - // Due to bug 751834 it is not possible document and define these - // properties in the prototype. - - /** - * An hashmap that contains the parameters of the Data URL. By default is - * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"} - */ - this.parameters = {}; - - /** - * The MIME type of the data. By default is empty, that accordingly to RFC - * is equivalent to "text/plain" - */ - this.mimeType = ""; - - /** - * The string that represent the data in the Data URL - */ - this.data = ""; - - if (typeof uri === "undefined") - return; - - uri = String(uri); - - let matches = uri.match(/^data:([^,]*),(.*)$/i); - - if (!matches) - throw new URIError("Malformed Data URL: " + uri); - - let mediaType = matches[1].trim(); - - this.data = decodeURIComponent(matches[2].trim()); - - if (!mediaType) - return; - - let parametersList = mediaType.split(";"); - - this.mimeType = parametersList.shift().trim(); - - for (let parameter, i = 0; parameter = parametersList[i++];) { - let pairs = parameter.split("="); - let name = pairs[0].trim(); - let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : ""; - - this.parameters[name] = value; - } - - if (this.base64) - this.data = base64.decode(this.data); - - }, - - /** - * Returns the object as a valid Data URL string - * - * @returns {String} The Data URL - */ - toString : function() { - let parametersList = []; - - for (let name in this.parameters) { - let encodedParameter = encodeURIComponent(name); - let value = this.parameters[name]; - - if (value) - encodedParameter += "=" + encodeURIComponent(value); - - parametersList.push(encodedParameter); - } - - // If there is at least a parameter, add an empty string in order - // to start with a `;` on join call. - if (parametersList.length > 0) - parametersList.unshift(""); - - let data = this.base64 ? base64.encode(this.data) : this.data; - - return "data:" + - this.mimeType + - parametersList.join(";") + "," + - encodeURIComponent(data); - } -}); - -exports.DataURL = DataURL; - -var getTLD = exports.getTLD = function getTLD (url) { - let uri = newURI(url.toString()); - let tld = null; - try { - tld = tlds.getPublicSuffix(uri); - } - catch (e) { - if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS && - e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) { - throw e; - } - } - return tld; -}; - -var isValidURI = exports.isValidURI = function (uri) { - try { - newURI(uri); - } - catch(e) { - return false; - } - return true; -} - -function isLocalURL(url) { - if (String.indexOf(url, './') === 0) - return true; - - try { - return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1; - } - catch(e) {} - - return false; -} -exports.isLocalURL = isLocalURL; diff --git a/addon-sdk/source/lib/sdk/url/utils.js b/addon-sdk/source/lib/sdk/url/utils.js deleted file mode 100644 index aa5759204..000000000 --- a/addon-sdk/source/lib/sdk/url/utils.js +++ /dev/null @@ -1,29 +0,0 @@ -/* 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": "experimental" -}; - -const { Cc, Ci, Cr } = require("chrome"); -const IOService = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); -const { isValidURI } = require("../url"); -const { method } = require("../../method/core"); - -function newURI (uri) { - if (!isValidURI(uri)) - throw new Error("malformed URI: " + uri); - return IOService.newURI(uri, null, null); -} -exports.newURI = newURI; - -var getURL = method('sdk/url:getURL'); -getURL.define(String, url => url); -getURL.define(function (object) { - return null; -}); -exports.getURL = getURL; diff --git a/addon-sdk/source/lib/sdk/util/array.js b/addon-sdk/source/lib/sdk/util/array.js deleted file mode 100644 index 1d61a973e..000000000 --- a/addon-sdk/source/lib/sdk/util/array.js +++ /dev/null @@ -1,123 +0,0 @@ -/* 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": "experimental" -}; - -/** - * Returns `true` if given `array` contain given `element` or `false` - * otherwise. - * @param {Array} array - * Target array. - * @param {Object|String|Number|Boolean} element - * Element being looked up. - * @returns {Boolean} - */ -var has = exports.has = function has(array, element) { - // shorter and faster equivalent of `array.indexOf(element) >= 0` - return !!~array.indexOf(element); -}; -var hasAny = exports.hasAny = function hasAny(array, elements) { - if (arguments.length < 2) - return false; - if (!Array.isArray(elements)) - elements = [ elements ]; - return array.some(function (element) { - return has(elements, element); - }); -}; - -/** - * Adds given `element` to the given `array` if it does not contain it yet. - * `true` is returned if element was added otherwise `false` is returned. - * @param {Array} array - * Target array. - * @param {Object|String|Number|Boolean} element - * Element to be added. - * @returns {Boolean} - */ -var add = exports.add = function add(array, element) { - var result; - if ((result = !has(array, element))) - array.push(element); - - return result; -}; - -/** - * Removes first occurrence of the given `element` from the given `array`. If - * `array` does not contain given `element` `false` is returned otherwise - * `true` is returned. - * @param {Array} array - * Target array. - * @param {Object|String|Number|Boolean} element - * Element to be removed. - * @returns {Boolean} - */ -exports.remove = function remove(array, element) { - var result; - if ((result = has(array, element))) - array.splice(array.indexOf(element), 1); - - return result; -}; - -/** - * Produces a duplicate-free version of the given `array`. - * @param {Array} array - * Source array. - * @returns {Array} - */ -function unique(array) { - return array.reduce(function(result, item) { - add(result, item); - return result; - }, []); -}; -exports.unique = unique; - -/** - * Produce an array that contains the union: each distinct element from all - * of the passed-in arrays. - */ -function union() { - return unique(Array.concat.apply(null, arguments)); -}; -exports.union = union; - -exports.flatten = function flatten(array){ - var flat = []; - for (var i = 0, l = array.length; i < l; i++) { - flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]); - } - return flat; -}; - -function fromIterator(iterator) { - let array = []; - if (iterator.__iterator__) { - for (let item of iterator) - array.push(item); - } - else { - for (let item of iterator) - array.push(item); - } - return array; -} -exports.fromIterator = fromIterator; - -function find(array, predicate, fallback) { - var index = 0; - var count = array.length; - while (index < count) { - var value = array[index]; - if (predicate(value)) return value; - else index = index + 1; - } - return fallback; -} -exports.find = find; diff --git a/addon-sdk/source/lib/sdk/util/collection.js b/addon-sdk/source/lib/sdk/util/collection.js deleted file mode 100644 index 194a29470..000000000 --- a/addon-sdk/source/lib/sdk/util/collection.js +++ /dev/null @@ -1,115 +0,0 @@ -/* 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": "experimental" -}; - -exports.Collection = Collection; - -/** - * Adds a collection property to the given object. Setting the property to a - * scalar value empties the collection and adds the value. Setting it to an - * array empties the collection and adds all the items in the array. - * - * @param obj - * The property will be defined on this object. - * @param propName - * The name of the property. - * @param array - * If given, this will be used as the collection's backing array. - */ -exports.addCollectionProperty = function addCollProperty(obj, propName, array) { - array = array || []; - let publicIface = new Collection(array); - - Object.defineProperty(obj, propName, { - configurable: true, - enumerable: true, - - set: function set(itemOrItems) { - array.splice(0, array.length); - publicIface.add(itemOrItems); - }, - - get: function get() { - return publicIface; - } - }); -}; - -/** - * A collection is ordered, like an array, but its items are unique, like a set. - * - * @param array - * The collection is backed by an array. If this is given, it will be - * used as the backing array. This way the caller can fully control the - * collection. Otherwise a new empty array will be used, and no one but - * the collection will have access to it. - */ -function Collection(array) { - array = array || []; - - /** - * Provides iteration over the collection. Items are yielded in the order - * they were added. - */ - this.__iterator__ = function Collection___iterator__() { - let items = array.slice(); - for (let i = 0; i < items.length; i++) - yield items[i]; - }; - - /** - * The number of items in the collection. - */ - this.__defineGetter__("length", function Collection_get_length() { - return array.length; - }); - - /** - * Adds a single item or an array of items to the collection. Any items - * already contained in the collection are ignored. - * - * @param itemOrItems - * An item or array of items. - * @return The collection. - */ - this.add = function Collection_add(itemOrItems) { - let items = toArray(itemOrItems); - for (let i = 0; i < items.length; i++) { - let item = items[i]; - if (array.indexOf(item) < 0) - array.push(item); - } - return this; - }; - - /** - * Removes a single item or an array of items from the collection. Any items - * not contained in the collection are ignored. - * - * @param itemOrItems - * An item or array of items. - * @return The collection. - */ - this.remove = function Collection_remove(itemOrItems) { - let items = toArray(itemOrItems); - for (let i = 0; i < items.length; i++) { - let idx = array.indexOf(items[i]); - if (idx >= 0) - array.splice(idx, 1); - } - return this; - }; -}; - -function toArray(itemOrItems) { - let isArr = itemOrItems && - itemOrItems.constructor && - itemOrItems.constructor.name === "Array"; - return isArr ? itemOrItems : [itemOrItems]; -} diff --git a/addon-sdk/source/lib/sdk/util/contract.js b/addon-sdk/source/lib/sdk/util/contract.js deleted file mode 100644 index c689ea601..000000000 --- a/addon-sdk/source/lib/sdk/util/contract.js +++ /dev/null @@ -1,55 +0,0 @@ -/* 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 { validateOptions: valid } = require("../deprecated/api-utils"); -const method = require("method/core"); - -// Function takes property validation rules and returns function that given -// an `options` object will return validated / normalized options back. If -// option(s) are invalid validator will throw exception described by rules. -// Returned will also have contain `rules` property with a given validation -// rules and `properties` function that can be used to generate validated -// property getter and setters can be mixed into prototype. For more details -// see `properties` function below. -function contract(rules) { - const validator = (instance, options) => { - return valid(options || instance || {}, rules); - }; - validator.rules = rules - validator.properties = function(modelFor) { - return properties(modelFor, rules); - } - return validator; -} -exports.contract = contract - -// Function takes `modelFor` instance state model accessor functions and -// a property validation rules and generates object with getters and setters -// that can be mixed into prototype. Property accessors update model for the -// given instance. If you wish to react to property updates you can always -// override setters to put specific logic. -function properties(modelFor, rules) { - let descriptor = Object.keys(rules).reduce(function(descriptor, name) { - descriptor[name] = { - get: function() { return modelFor(this)[name] }, - set: function(value) { - let change = {}; - change[name] = value; - modelFor(this)[name] = valid(change, rules)[name]; - } - } - return descriptor - }, {}); - return Object.create(Object.prototype, descriptor); -} -exports.properties = properties; - -const validate = method("contract/validate"); -exports.validate = validate; diff --git a/addon-sdk/source/lib/sdk/util/deprecate.js b/addon-sdk/source/lib/sdk/util/deprecate.js deleted file mode 100644 index 40f236de5..000000000 --- a/addon-sdk/source/lib/sdk/util/deprecate.js +++ /dev/null @@ -1,40 +0,0 @@ -/* 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": "experimental" -}; - -const { get, format } = require("../console/traceback"); -const { get: getPref } = require("../preferences/service"); -const PREFERENCE = "devtools.errorconsole.deprecation_warnings"; - -function deprecateUsage(msg) { - // Print caller stacktrace in order to help figuring out which code - // does use deprecated thing - let stack = get().slice(2); - - if (getPref(PREFERENCE)) - console.error("DEPRECATED: " + msg + "\n" + format(stack)); -} -exports.deprecateUsage = deprecateUsage; - -function deprecateFunction(fun, msg) { - return function deprecated() { - deprecateUsage(msg); - return fun.apply(this, arguments); - }; -} -exports.deprecateFunction = deprecateFunction; - -function deprecateEvent(fun, msg, evtTypes) { - return function deprecateEvent(evtType) { - if (evtTypes.indexOf(evtType) >= 0) - deprecateUsage(msg); - return fun.apply(this, arguments); - }; -} -exports.deprecateEvent = deprecateEvent; diff --git a/addon-sdk/source/lib/sdk/util/dispatcher.js b/addon-sdk/source/lib/sdk/util/dispatcher.js deleted file mode 100644 index 67d29dfed..000000000 --- a/addon-sdk/source/lib/sdk/util/dispatcher.js +++ /dev/null @@ -1,54 +0,0 @@ -/* 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": "experimental" -}; - -const method = require("method/core"); - -// Utility function that is just an enhancement over `method` to -// allow predicate based dispatch in addition to polymorphic -// dispatch. Unfortunately polymorphic dispatch does not quite -// cuts it in the world of XPCOM where no types / classes exist -// and all the XUL nodes share same type / prototype. -// Probably this is more generic and belongs some place else, but -// we can move it later once this will be relevant. -var dispatcher = hint => { - const base = method(hint); - // Make a map for storing predicate, implementation mappings. - let implementations = new Map(); - - // Dispatcher function goes through `predicate, implementation` - // pairs to find predicate that matches first argument and - // returns application of arguments on the associated - // `implementation`. If no matching predicate is found delegates - // to a `base` polymorphic function. - let dispatch = (value, ...rest) => { - for (let [predicate, implementation] of implementations) { - if (predicate(value)) - return implementation(value, ...rest); - } - - return base(value, ...rest); - }; - - // Expose base API. - dispatch.define = base.define; - dispatch.implement = base.implement; - dispatch.toString = base.toString; - - // Add a `when` function to allow extending function via - // predicates. - dispatch.when = (predicate, implementation) => { - if (implementations.has(predicate)) - throw TypeError("Already implemented for the given predicate"); - implementations.set(predicate, implementation); - }; - - return dispatch; -}; - -exports.dispatcher = dispatcher; diff --git a/addon-sdk/source/lib/sdk/util/list.js b/addon-sdk/source/lib/sdk/util/list.js deleted file mode 100644 index 6d7d2dea9..000000000 --- a/addon-sdk/source/lib/sdk/util/list.js +++ /dev/null @@ -1,90 +0,0 @@ -/* 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": "experimental" -}; - -const { Class } = require('../core/heritage'); -const listNS = require('../core/namespace').ns(); - -const listOptions = { - /** - * List constructor can take any number of element to populate itself. - * @params {Object|String|Number} element - * @example - * List(1,2,3).length == 3 // true - */ - initialize: function List() { - listNS(this).keyValueMap = []; - - for (let i = 0, ii = arguments.length; i < ii; i++) - addListItem(this, arguments[i]); - }, - /** - * Number of elements in this list. - * @type {Number} - */ - get length() { - return listNS(this).keyValueMap.length; - }, - /** - * Returns a string representing this list. - * @returns {String} - */ - toString: function toString() { - return 'List(' + listNS(this).keyValueMap + ')'; - }, - /** - * Custom iterator providing `List`s enumeration behavior. - * We cant reuse `_iterator` that is defined by `Iterable` since it provides - * iteration in an arbitrary order. - * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in - * @param {Boolean} onKeys - */ - __iterator__: function __iterator__(onKeys, onKeyValue) { - let array = listNS(this).keyValueMap.slice(0), - i = -1; - for (let element of array) - yield onKeyValue ? [++i, element] : onKeys ? ++i : element; - }, -}; -listOptions[Symbol.iterator] = function iterator() { - return listNS(this).keyValueMap.slice(0)[Symbol.iterator](); -}; -const List = Class(listOptions); -exports.List = List; - -function addListItem(that, value) { - let list = listNS(that).keyValueMap, - index = list.indexOf(value); - - if (-1 === index) { - try { - that[that.length] = value; - } - catch (e) {} - list.push(value); - } -} -exports.addListItem = addListItem; - -function removeListItem(that, element) { - let list = listNS(that).keyValueMap, - index = list.indexOf(element); - - if (0 <= index) { - list.splice(index, 1); - try { - for (let length = list.length; index < length; index++) - that[index] = list[index]; - that[list.length] = undefined; - } - catch(e){} - } -} -exports.removeListItem = removeListItem; - -exports.listNS = listNS; diff --git a/addon-sdk/source/lib/sdk/util/match-pattern.js b/addon-sdk/source/lib/sdk/util/match-pattern.js deleted file mode 100644 index a0eb88b49..000000000 --- a/addon-sdk/source/lib/sdk/util/match-pattern.js +++ /dev/null @@ -1,113 +0,0 @@ -/* 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 { URL } = require('../url'); -const cache = {}; - -function MatchPattern(pattern) { - if (cache[pattern]) return cache[pattern]; - - if (typeof pattern.test == "function") { - // For compatibility with -moz-document rules, we require the RegExp's - // global, ignoreCase, and multiline flags to be set to false. - if (pattern.global) { - throw new Error("A RegExp match pattern cannot be set to `global` " + - "(i.e. //g)."); - } - if (pattern.multiline) { - throw new Error("A RegExp match pattern cannot be set to `multiline` " + - "(i.e. //m)."); - } - - this.regexp = pattern; - } - else { - let firstWildcardPosition = pattern.indexOf("*"); - let lastWildcardPosition = pattern.lastIndexOf("*"); - if (firstWildcardPosition != lastWildcardPosition) - throw new Error("There can be at most one '*' character in a wildcard."); - - if (firstWildcardPosition == 0) { - if (pattern.length == 1) - this.anyWebPage = true; - else if (pattern[1] != ".") - throw new Error("Expected a *.<domain name> string, got: " + pattern); - else - this.domain = pattern.substr(2); - } - else { - if (pattern.indexOf(":") == -1) { - throw new Error("When not using *.example.org wildcard, the string " + - "supplied is expected to be either an exact URL to " + - "match or a URL prefix. The provided string ('" + - pattern + "') is unlikely to match any pages."); - } - - if (firstWildcardPosition == -1) - this.exactURL = pattern; - else if (firstWildcardPosition == pattern.length - 1) - this.urlPrefix = pattern.substr(0, pattern.length - 1); - else { - throw new Error("The provided wildcard ('" + pattern + "') has a '*' " + - "in an unexpected position. It is expected to be the " + - "first or the last character in the wildcard."); - } - } - } - - cache[pattern] = this; -} - -MatchPattern.prototype = { - test: function MatchPattern_test(urlStr) { - try { - var url = URL(urlStr); - } - catch (err) { - return false; - } - - // Test the URL against a RegExp pattern. For compatibility with - // -moz-document rules, we require the RegExp to match the entire URL, - // so we not only test for a match, we also make sure the matched string - // is the entire URL string. - // - // Assuming most URLs don't match most match patterns, we call `test` for - // speed when determining whether or not the URL matches, then call `exec` - // for the small subset that match to make sure the entire URL matches. - if (this.regexp && this.regexp.test(urlStr) && - this.regexp.exec(urlStr)[0] == urlStr) - return true; - - if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme)) - return true; - - if (this.exactURL && this.exactURL == urlStr) - return true; - - // Tests the urlStr against domain and check if - // wildcard submitted (*.domain.com), it only allows - // subdomains (sub.domain.com) or from the root (http://domain.com) - // and reject non-matching domains (otherdomain.com) - // bug 856913 - if (this.domain && url.host && - (url.host === this.domain || - url.host.slice(-this.domain.length - 1) === "." + this.domain)) - return true; - - if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix)) - return true; - - return false; - }, - - toString: () => '[object MatchPattern]' -}; - -exports.MatchPattern = MatchPattern; diff --git a/addon-sdk/source/lib/sdk/util/object.js b/addon-sdk/source/lib/sdk/util/object.js deleted file mode 100644 index 9d202bb51..000000000 --- a/addon-sdk/source/lib/sdk/util/object.js +++ /dev/null @@ -1,104 +0,0 @@ -/* 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 { flatten } = require('./array'); - -/** - * Merges all the properties of all arguments into first argument. If two or - * more argument objects have own properties with the same name, the property - * is overridden, with precedence from right to left, implying, that properties - * of the object on the left are overridden by a same named property of the - * object on the right. - * - * Any argument given with "falsy" value - commonly `null` and `undefined` in - * case of objects - are skipped. - * - * @examples - * var a = { bar: 0, a: 'a' } - * var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' }); - * b === a // true - * b.a // 'a' - * b.foo // 'bar' - * b.bar // 1 - * b.name // 'b' - */ -function merge(source) { - let descriptor = {}; - - // `Boolean` converts the first parameter to a boolean value. Any object is - // converted to `true` where `null` and `undefined` becames `false`. Therefore - // the `filter` method will keep only objects that are defined and not null. - Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) { - getOwnPropertyIdentifiers(properties).forEach(function(name) { - descriptor[name] = Object.getOwnPropertyDescriptor(properties, name); - }); - }); - return Object.defineProperties(source, descriptor); -} -exports.merge = merge; - -/** - * Returns an object that inherits from the first argument and contains all the - * properties from all following arguments. - * `extend(source1, source2, source3)` is equivalent of - * `merge(Object.create(source1), source2, source3)`. - */ -function extend(source) { - let rest = Array.slice(arguments, 1); - rest.unshift(Object.create(source)); - return merge.apply(null, rest); -} -exports.extend = extend; - -function has(obj, key) { - return obj.hasOwnProperty(key); -} -exports.has = has; - -function each(obj, fn) { - for (let key in obj) has(obj, key) && fn(obj[key], key, obj); -} -exports.each = each; - -/** - * Like `merge`, except no property descriptors are manipulated, for use - * with platform objects. Identical to underscore's `extend`. Useful for - * merging XPCOM objects - */ -function safeMerge(source) { - Array.slice(arguments, 1).forEach(function onEach (obj) { - for (let prop in obj) source[prop] = obj[prop]; - }); - return source; -} -exports.safeMerge = safeMerge; - -/* - * Returns a copy of the object without omitted properties - */ -function omit(source, ...values) { - let copy = {}; - let keys = flatten(values); - for (let prop in source) - if (!~keys.indexOf(prop)) - copy[prop] = source[prop]; - return copy; -} -exports.omit = omit; - -// get object's own property Symbols and/or Names, including nonEnumerables by default -function getOwnPropertyIdentifiers(object, options = { names: true, symbols: true, nonEnumerables: true }) { - const symbols = !options.symbols ? [] : - Object.getOwnPropertySymbols(object); - const names = !options.names ? [] : - options.nonEnumerables ? Object.getOwnPropertyNames(object) : - Object.keys(object); - return [...names, ...symbols]; -} -exports.getOwnPropertyIdentifiers = getOwnPropertyIdentifiers; diff --git a/addon-sdk/source/lib/sdk/util/rules.js b/addon-sdk/source/lib/sdk/util/rules.js deleted file mode 100644 index 98e3109b0..000000000 --- a/addon-sdk/source/lib/sdk/util/rules.js +++ /dev/null @@ -1,53 +0,0 @@ -/* 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 { Class } = require('../core/heritage'); -const { MatchPattern } = require('./match-pattern'); -const { emit } = require('../event/core'); -const { EventTarget } = require('../event/target'); -const { List, addListItem, removeListItem } = require('./list'); - -// Should deprecate usage of EventEmitter/compose -const Rules = Class({ - implements: [ - EventTarget, - List - ], - add: function(...rules) { - return [].concat(rules).forEach(function onAdd(rule) { - addListItem(this, rule); - emit(this, 'add', rule); - }, this); - }, - remove: function(...rules) { - return [].concat(rules).forEach(function onRemove(rule) { - removeListItem(this, rule); - emit(this, 'remove', rule); - }, this); - }, - get: function(rule) { - let found = false; - for (let i in this) if (this[i] === rule) found = true; - return found; - }, - // Returns true if uri matches atleast one stored rule - matchesAny: function(uri) { - return !!filterMatches(this, uri).length; - }, - toString: () => '[object Rules]' -}); -exports.Rules = Rules; - -function filterMatches(instance, uri) { - let matches = []; - for (let i in instance) { - if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]); - } - return matches; -} diff --git a/addon-sdk/source/lib/sdk/util/sequence.js b/addon-sdk/source/lib/sdk/util/sequence.js deleted file mode 100644 index 28e3de255..000000000 --- a/addon-sdk/source/lib/sdk/util/sequence.js +++ /dev/null @@ -1,593 +0,0 @@ -/* 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": "experimental" -}; - -// Disclamer: -// In this module we'll have some common argument / variable names -// to hint their type or behavior. -// -// - `f` stands for "function" that is intended to be side effect -// free. -// - `p` stands for "predicate" that is function which returns logical -// true or false and is intended to be side effect free. -// - `x` / `y` single item of the sequence. -// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies -// type of the items in sequence, so sequence is not of the same item. -// - `_` used for argument(s) or variable(s) who's values are ignored. - -const { complement, flip, identity } = require("../lang/functional"); -const { isArray, isArguments, isMap, isSet, isGenerator, - isString, isBoolean, isNumber } = require("../lang/type"); - -const Sequence = function Sequence(iterator) { - if (!isGenerator(iterator)) { - throw TypeError("Expected generator argument"); - } - - this[Symbol.iterator] = iterator; -}; -exports.Sequence = Sequence; - -const polymorphic = dispatch => x => - x === null ? dispatch.null(null) : - x === void(0) ? dispatch.void(void(0)) : - isArray(x) ? (dispatch.array || dispatch.indexed)(x) : - isString(x) ? (dispatch.string || dispatch.indexed)(x) : - isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) : - isMap(x) ? dispatch.map(x) : - isSet(x) ? dispatch.set(x) : - isNumber(x) ? dispatch.number(x) : - isBoolean(x) ? dispatch.boolean(x) : - dispatch.default(x); - -const nogen = function*() {}; -const empty = () => new Sequence(nogen); -exports.empty = empty; - -const seq = polymorphic({ - null: empty, - void: empty, - array: identity, - string: identity, - arguments: identity, - map: identity, - set: identity, - default: x => x instanceof Sequence ? x : new Sequence(x) -}); -exports.seq = seq; - -// Function to cast seq to string. -const string = (...etc) => "".concat(...etc); -exports.string = string; - -// Function for casting seq to plain object. -const object = (...pairs) => { - let result = {}; - for (let [key, value] of pairs) - result[key] = value; - - return result; -}; -exports.object = object; - -// Takes `getEnumerator` function that returns `nsISimpleEnumerator` -// and creates lazy sequence of it's items. Note that function does -// not take `nsISimpleEnumerator` itslef because that would allow -// single iteration, which would not be consistent with rest of the -// lazy sequences. -const fromEnumerator = getEnumerator => seq(function* () { - const enumerator = getEnumerator(); - while (enumerator.hasMoreElements()) - yield enumerator.getNext(); -}); -exports.fromEnumerator = fromEnumerator; - -// Takes `object` and returns lazy sequence of own `[key, value]` -// pairs (does not include inherited and non enumerable keys). -const pairs = polymorphic({ - null: empty, - void: empty, - map: identity, - indexed: indexed => seq(function* () { - const count = indexed.length; - let index = 0; - while (index < count) { - yield [index, indexed[index]]; - index = index + 1; - } - }), - default: object => seq(function* () { - for (let key of Object.keys(object)) - yield [key, object[key]]; - }) -}); -exports.pairs = pairs; - -const names = polymorphic({ - null: empty, - void: empty, - default: object => seq(function*() { - for (let name of Object.getOwnPropertyNames(object)) { - yield name; - } - }) -}); -exports.names = names; - -const symbols = polymorphic({ - null: empty, - void: empty, - default: object => seq(function* () { - for (let symbol of Object.getOwnPropertySymbols(object)) { - yield symbol; - } - }) -}); -exports.symbols = symbols; - -const keys = polymorphic({ - null: empty, - void: empty, - indexed: indexed => seq(function* () { - const count = indexed.length; - let index = 0; - while (index < count) { - yield index; - index = index + 1; - } - }), - map: map => seq(function* () { - for (let [key, _] of map) - yield key; - }), - default: object => seq(function* () { - for (let key of Object.keys(object)) - yield key; - }) -}); -exports.keys = keys; - - -const values = polymorphic({ - null: empty, - void: empty, - set: identity, - indexed: indexed => seq(function* () { - const count = indexed.length; - let index = 0; - while (index < count) { - yield indexed[index]; - index = index + 1; - } - }), - map: map => seq(function* () { - for (let [_, value] of map) yield value; - }), - default: object => seq(function* () { - for (let key of Object.keys(object)) yield object[key]; - }) -}); -exports.values = values; - - - -// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc. -// `f` must be free of side-effects. Note that returned -// sequence is infinite so it must be consumed partially. -// -// Implements clojure iterate: -// http://clojuredocs.org/clojure_core/clojure.core/iterate -const iterate = (f, x) => seq(function* () { - let state = x; - while (true) { - yield state; - state = f(state); - } -}); -exports.iterate = iterate; - -// Returns a lazy sequence of the items in sequence for which `p(item)` -// returns `true`. `p` must be free of side-effects. -// -// Implements clojure filter: -// http://clojuredocs.org/clojure_core/clojure.core/filter -const filter = (p, sequence) => seq(function* () { - if (sequence !== null && sequence !== void(0)) { - for (let item of sequence) { - if (p(item)) - yield item; - } - } -}); -exports.filter = filter; - -// Returns a lazy sequence consisting of the result of applying `f` to the -// set of first items of each sequence, followed by applying f to the set -// of second items in each sequence, until any one of the sequences is -// exhausted. Any remaining items in other sequences are ignored. Function -// `f` should accept number-of-sequences arguments. -// -// Implements clojure map: -// http://clojuredocs.org/clojure_core/clojure.core/map -const map = (f, ...sequences) => seq(function* () { - const count = sequences.length; - // Optimize a single sequence case - if (count === 1) { - let [sequence] = sequences; - if (sequence !== null && sequence !== void(0)) { - for (let item of sequence) - yield f(item); - } - } - else { - // define args array that will be recycled on each - // step to aggregate arguments to be passed to `f`. - let args = []; - // define inputs to contain started generators. - let inputs = []; - - let index = 0; - while (index < count) { - inputs[index] = sequences[index][Symbol.iterator](); - index = index + 1; - } - - // Run loop yielding of applying `f` to the set of - // items at each step until one of the `inputs` is - // exhausted. - let done = false; - while (!done) { - let index = 0; - let value = void(0); - while (index < count && !done) { - ({ done, value } = inputs[index].next()); - - // If input is not exhausted yet store value in args. - if (!done) { - args[index] = value; - index = index + 1; - } - } - - // If none of the inputs is exhasted yet, `args` contain items - // from each input so we yield application of `f` over them. - if (!done) - yield f(...args); - } - } -}); -exports.map = map; - -// Returns a lazy sequence of the intermediate values of the reduction (as -// per reduce) of sequence by `f`, starting with `initial` value if provided. -// -// Implements clojure reductions: -// http://clojuredocs.org/clojure_core/clojure.core/reductions -const reductions = (...params) => { - const count = params.length; - let hasInitial = false; - let f, initial, source; - if (count === 2) { - [f, source] = params; - } - else if (count === 3) { - [f, initial, source] = params; - hasInitial = true; - } - else { - throw Error("Invoked with wrong number of arguments: " + count); - } - - const sequence = seq(source); - - return seq(function* () { - let started = hasInitial; - let result = void(0); - - // If initial is present yield it. - if (hasInitial) - yield (result = initial); - - // For each item of the sequence accumulate new result. - for (let item of sequence) { - // If nothing has being yield yet set result to first - // item and yield it. - if (!started) { - started = true; - yield (result = item); - } - // Otherwise accumulate new result and yield it. - else { - yield (result = f(result, item)); - } - } - - // If nothing has being yield yet it's empty sequence and no - // `initial` was provided in which case we need to yield `f()`. - if (!started) - yield f(); - }); -}; -exports.reductions = reductions; - -// `f` should be a function of 2 arguments. If `initial` is not supplied, -// returns the result of applying `f` to the first 2 items in sequence, then -// applying `f` to that result and the 3rd item, etc. If sequence contains no -// items, `f` must accept no arguments as well, and reduce returns the -// result of calling f with no arguments. If sequence has only 1 item, it -// is returned and `f` is not called. If `initial` is supplied, returns the -// result of applying `f` to `initial` and the first item in sequence, then -// applying `f` to that result and the 2nd item, etc. If sequence contains no -// items, returns `initial` and `f` is not called. -// -// Implements clojure reduce: -// http://clojuredocs.org/clojure_core/clojure.core/reduce -const reduce = (...args) => { - const xs = reductions(...args); - let x; - for (x of xs) void(0); - return x; -}; -exports.reduce = reduce; - -const each = (f, sequence) => { - for (let x of seq(sequence)) void(f(x)); -}; -exports.each = each; - - -const inc = x => x + 1; -// Returns the number of items in the sequence. `count(null)` && `count()` -// returns `0`. Also works on strings, arrays, Maps & Sets. - -// Implements clojure count: -// http://clojuredocs.org/clojure_core/clojure.core/count -const count = polymorphic({ - null: _ => 0, - void: _ => 0, - indexed: indexed => indexed.length, - map: map => map.size, - set: set => set.size, - default: xs => reduce(inc, 0, xs) -}); -exports.count = count; - -// Returns `true` if sequence has no items. - -// Implements clojure empty?: -// http://clojuredocs.org/clojure_core/clojure.core/empty_q -const isEmpty = sequence => { - // Treat `null` and `undefined` as empty sequences. - if (sequence === null || sequence === void(0)) - return true; - - // If contains any item non empty so return `false`. - for (let _ of sequence) - return false; - - // If has not returned yet, there was nothing to iterate - // so it's empty. - return true; -}; -exports.isEmpty = isEmpty; - -const and = (a, b) => a && b; - -// Returns true if `p(x)` is logical `true` for every `x` in sequence, else -// `false`. -// -// Implements clojure every?: -// http://clojuredocs.org/clojure_core/clojure.core/every_q -const isEvery = (p, sequence) => { - if (sequence !== null && sequence !== void(0)) { - for (let item of sequence) { - if (!p(item)) - return false; - } - } - return true; -}; -exports.isEvery = isEvery; - -// Returns the first logical true value of (p x) for any x in sequence, -// else `null`. -// -// Implements clojure some: -// http://clojuredocs.org/clojure_core/clojure.core/some -const some = (p, sequence) => { - if (sequence !== null && sequence !== void(0)) { - for (let item of sequence) { - if (p(item)) - return true; - } - } - return null; -}; -exports.some = some; - -// Returns a lazy sequence of the first `n` items in sequence, or all items if -// there are fewer than `n`. -// -// Implements clojure take: -// http://clojuredocs.org/clojure_core/clojure.core/take -const take = (n, sequence) => n <= 0 ? empty() : seq(function* () { - let count = n; - for (let item of sequence) { - yield item; - count = count - 1; - if (count === 0) break; - } -}); -exports.take = take; - -// Returns a lazy sequence of successive items from sequence while -// `p(item)` returns `true`. `p` must be free of side-effects. -// -// Implements clojure take-while: -// http://clojuredocs.org/clojure_core/clojure.core/take-while -const takeWhile = (p, sequence) => seq(function* () { - for (let item of sequence) { - if (!p(item)) - break; - - yield item; - } -}); -exports.takeWhile = takeWhile; - -// Returns a lazy sequence of all but the first `n` items in -// sequence. -// -// Implements clojure drop: -// http://clojuredocs.org/clojure_core/clojure.core/drop -const drop = (n, sequence) => seq(function* () { - if (sequence !== null && sequence !== void(0)) { - let count = n; - for (let item of sequence) { - if (count > 0) - count = count - 1; - else - yield item; - } - } -}); -exports.drop = drop; - -// Returns a lazy sequence of the items in sequence starting from the -// first item for which `p(item)` returns falsy value. -// -// Implements clojure drop-while: -// http://clojuredocs.org/clojure_core/clojure.core/drop-while -const dropWhile = (p, sequence) => seq(function* () { - let keep = false; - for (let item of sequence) { - keep = keep || !p(item); - if (keep) yield item; - } -}); -exports.dropWhile = dropWhile; - -// Returns a lazy sequence representing the concatenation of the -// suplied sequences. -// -// Implements clojure conact: -// http://clojuredocs.org/clojure_core/clojure.core/concat -const concat = (...sequences) => seq(function* () { - for (let sequence of sequences) - for (let item of sequence) - yield item; -}); -exports.concat = concat; - -// Returns the first item in the sequence. -// -// Implements clojure first: -// http://clojuredocs.org/clojure_core/clojure.core/first -const first = sequence => { - if (sequence !== null && sequence !== void(0)) { - for (let item of sequence) - return item; - } - return null; -}; -exports.first = first; - -// Returns a possibly empty sequence of the items after the first. -// -// Implements clojure rest: -// http://clojuredocs.org/clojure_core/clojure.core/rest -const rest = sequence => drop(1, sequence); -exports.rest = rest; - -// Returns the value at the index. Returns `notFound` or `undefined` -// if index is out of bounds. -const nth = (xs, n, notFound) => { - if (n >= 0) { - if (isArray(xs) || isArguments(xs) || isString(xs)) { - return n < xs.length ? xs[n] : notFound; - } - else if (xs !== null && xs !== void(0)) { - let count = n; - for (let x of xs) { - if (count <= 0) - return x; - - count = count - 1; - } - } - } - return notFound; -}; -exports.nth = nth; - -// Return the last item in sequence, in linear time. -// If `sequence` is an array or string or arguments -// returns in constant time. -// Implements clojure last: -// http://clojuredocs.org/clojure_core/clojure.core/last -const last = polymorphic({ - null: _ => null, - void: _ => null, - indexed: indexed => indexed[indexed.length - 1], - map: xs => reduce((_, x) => x, xs), - set: xs => reduce((_, x) => x, xs), - default: xs => reduce((_, x) => x, xs) -}); -exports.last = last; - -// Return a lazy sequence of all but the last `n` (default 1) items -// from the give `xs`. -// -// Implements clojure drop-last: -// http://clojuredocs.org/clojure_core/clojure.core/drop-last -const dropLast = flip((xs, n=1) => seq(function* () { - let ys = []; - for (let x of xs) { - ys.push(x); - if (ys.length > n) - yield ys.shift(); - } -})); -exports.dropLast = dropLast; - -// Returns a lazy sequence of the elements of `xs` with duplicates -// removed -// -// Implements clojure distinct -// http://clojuredocs.org/clojure_core/clojure.core/distinct -const distinct = sequence => seq(function* () { - let items = new Set(); - for (let item of sequence) { - if (!items.has(item)) { - items.add(item); - yield item; - } - } -}); -exports.distinct = distinct; - -// Returns a lazy sequence of the items in `xs` for which -// `p(x)` returns false. `p` must be free of side-effects. -// -// Implements clojure remove -// http://clojuredocs.org/clojure_core/clojure.core/remove -const remove = (p, xs) => filter(complement(p), xs); -exports.remove = remove; - -// Returns the result of applying concat to the result of -// `map(f, xs)`. Thus function `f` should return a sequence. -// -// Implements clojure mapcat -// http://clojuredocs.org/clojure_core/clojure.core/mapcat -const mapcat = (f, sequence) => seq(function* () { - const sequences = map(f, sequence); - for (let sequence of sequences) - for (let item of sequence) - yield item; -}); -exports.mapcat = mapcat; diff --git a/addon-sdk/source/lib/sdk/util/uuid.js b/addon-sdk/source/lib/sdk/util/uuid.js deleted file mode 100644 index 6d0f2de53..000000000 --- a/addon-sdk/source/lib/sdk/util/uuid.js +++ /dev/null @@ -1,19 +0,0 @@ -/* 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 { Cc, Ci, components: { ID: parseUUID } } = require('chrome'); -const { generateUUID } = Cc['@mozilla.org/uuid-generator;1']. - getService(Ci.nsIUUIDGenerator); - -// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned -// if not then new one is generated. -exports.uuid = function uuid(id) { - return id ? parseUUID(id) : generateUUID(); -}; diff --git a/addon-sdk/source/lib/sdk/view/core.js b/addon-sdk/source/lib/sdk/view/core.js deleted file mode 100644 index 5e82e9b5d..000000000 --- a/addon-sdk/source/lib/sdk/view/core.js +++ /dev/null @@ -1,26 +0,0 @@ -/* 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" -}; - -var { Ci } = require("chrome"); -var method = require("../../method/core"); - -// Returns DOM node associated with a view for -// the given `value`. If `value` has no view associated -// it returns `null`. You can implement this method for -// this type to define what the result should be for it. -var getNodeView = method("getNodeView"); -getNodeView.define(x => - x instanceof Ci.nsIDOMNode ? x : - x instanceof Ci.nsIDOMWindow ? x : - null); -exports.getNodeView = getNodeView; -exports.viewFor = getNodeView; - -var getActiveView = method("getActiveView"); -exports.getActiveView = getActiveView; diff --git a/addon-sdk/source/lib/sdk/webextension.js b/addon-sdk/source/lib/sdk/webextension.js deleted file mode 100644 index d1c4385e2..000000000 --- a/addon-sdk/source/lib/sdk/webextension.js +++ /dev/null @@ -1,43 +0,0 @@ -/* 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": "experimental" -}; - -let webExtension; -let waitForWebExtensionAPI; - -module.exports = { - initFromBootstrapAddonParam(data) { - if (webExtension) { - throw new Error("'sdk/webextension' module has been already initialized"); - } - - webExtension = data.webExtension; - }, - - startup() { - if (!webExtension) { - return Promise.reject(new Error( - "'sdk/webextension' module is currently disabled. " + - "('hasEmbeddedWebExtension' option is missing or set to false)" - )); - } - - // NOTE: calling `startup` more than once raises an "Embedded Extension already started" - // error, but given that SDK addons are going to have access to the startup method through - // an SDK module that can be required in any part of the addon, it will be nicer if any - // additional startup calls return the startup promise instead of raising an exception, - // so that the SDK addon can access the API object in the other addon modules without the - // need to manually pass this promise around. - if (!waitForWebExtensionAPI) { - waitForWebExtensionAPI = webExtension.startup(); - } - - return waitForWebExtensionAPI; - } -}; diff --git a/addon-sdk/source/lib/sdk/window/browser.js b/addon-sdk/source/lib/sdk/window/browser.js deleted file mode 100644 index 380b5a486..000000000 --- a/addon-sdk/source/lib/sdk/window/browser.js +++ /dev/null @@ -1,54 +0,0 @@ -/* 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 { windowNS } = require('./namespace'); -const { on, off, once } = require('../event/core'); -const { method } = require('../lang/functional'); -const { getWindowTitle } = require('./utils'); -const unload = require('../system/unload'); -const { EventTarget } = require('../event/target'); -const { isPrivate } = require('../private-browsing/utils'); -const { isWindowPrivate, isFocused } = require('../window/utils'); -const { viewFor } = require('../view/core'); - -const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead'; - -const BrowserWindow = Class({ - initialize: function initialize(options) { - EventTarget.prototype.initialize.call(this, options); - windowNS(this).window = options.window; - }, - activate: function activate() { - // TODO - return null; - }, - close: function() { - throw new Error(ERR_FENNEC_MSG); - return null; - }, - get title() { - return getWindowTitle(windowNS(this).window); - }, - // NOTE: Fennec only has one window, which is assumed below - // TODO: remove assumption below - // NOTE: tabs requires windows - get tabs() { - return require('../tabs'); - }, - get activeTab() { - return require('../tabs').activeTab; - }, - on: method(on), - removeListener: method(off), - once: method(once) -}); -exports.BrowserWindow = BrowserWindow; - -const getWindowView = window => windowNS(window).window; - -viewFor.define(BrowserWindow, getWindowView); -isPrivate.define(BrowserWindow, (window) => isWindowPrivate(viewFor(window).window)); -isFocused.define(BrowserWindow, (window) => isFocused(viewFor(window).window)); diff --git a/addon-sdk/source/lib/sdk/window/events.js b/addon-sdk/source/lib/sdk/window/events.js deleted file mode 100644 index b1d3a1f3e..000000000 --- a/addon-sdk/source/lib/sdk/window/events.js +++ /dev/null @@ -1,68 +0,0 @@ -/* 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 { Ci, Cu } = require("chrome"); -const { observe } = require("../event/chrome"); -const { open } = require("../event/dom"); -const { windows } = require("../window/utils"); -const { filter, merge, map, expand } = require("../event/utils"); - -function documentMatches(weakWindow, event) { - let window = weakWindow.get(); - return window && event.target === window.document; -} - -function makeStrictDocumentFilter(window) { - // Note: Do not define a closure within this function. Otherwise - // you may leak the window argument. - let weak = Cu.getWeakReference(window); - return documentMatches.bind(null, weak); -} - -function toEventWithDefaultViewTarget({type, target}) { - return { type: type, target: target.defaultView } -} - -// Function registers single shot event listeners for relevant window events -// that forward events to exported event stream. -function eventsFor(window) { - // NOTE: Do no use pass a closure from this function into a stream - // transform function. You will capture the window in the - // closure and leak the window until the event stream is - // completely closed. - let interactive = open(window, "DOMContentLoaded", { capture: true }); - let complete = open(window, "load", { capture: true }); - let states = merge([interactive, complete]); - let changes = filter(states, makeStrictDocumentFilter(window)); - return map(changes, toEventWithDefaultViewTarget); -} - -// Create our event channels. We do this in a separate function to -// minimize the chance of leaking intermediate objects on the global. -function makeEvents() { - // In addition to observing windows that are open we also observe windows - // that are already already opened in case they're in process of loading. - var opened = windows(null, { includePrivate: true }); - var currentEvents = merge(opened.map(eventsFor)); - - // Register system event listeners for top level window open / close. - function rename({type, target, data}) { - return { type: rename[type], target: target, data: data } - } - rename.domwindowopened = "open"; - rename.domwindowclosed = "close"; - - var openEvents = map(observe("domwindowopened"), rename); - var closeEvents = map(observe("domwindowclosed"), rename); - var futureEvents = expand(openEvents, ({target}) => eventsFor(target)); - - return merge([currentEvents, futureEvents, openEvents, closeEvents]); -} - -exports.events = makeEvents(); diff --git a/addon-sdk/source/lib/sdk/window/helpers.js b/addon-sdk/source/lib/sdk/window/helpers.js deleted file mode 100644 index 56cfcaba7..000000000 --- a/addon-sdk/source/lib/sdk/window/helpers.js +++ /dev/null @@ -1,81 +0,0 @@ -/* 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 { defer, all } = require('../core/promise'); -const events = require('../system/events'); -const { open: openWindow, onFocus, getToplevelWindow, - isInteractive, isStartupFinished, getOuterId } = require('./utils'); -const { Ci } = require("chrome"); - -function open(uri, options) { - return promise(openWindow.apply(null, arguments), 'load').then(focus); -} -exports.open = open; - -function close(window) { - let deferred = defer(); - let toplevelWindow = getToplevelWindow(window); - let outerId = getOuterId(toplevelWindow); - events.on("outer-window-destroyed", function onclose({subject}) { - let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; - if (id == outerId) { - events.off("outer-window-destroyed", onclose); - deferred.resolve(); - } - }, true); - window.close(); - return deferred.promise; -} -exports.close = close; - -function focus(window) { - let p = onFocus(window); - window.focus(); - return p; -} -exports.focus = focus; - -function ready(window) { - let { promise: result, resolve } = defer(); - - if (isInteractive(window)) - resolve(window); - else - resolve(promise(window, 'DOMContentLoaded')); - - return result; -} -exports.ready = ready; - -function startup(window) { - let { promise: result, resolve } = defer(); - - if (isStartupFinished(window)) { - resolve(window); - } else { - events.on("browser-delayed-startup-finished", function listener({subject}) { - if (subject === window) { - events.off("browser-delayed-startup-finished", listener); - resolve(window); - } - }); - } - - return result; -} -exports.startup = startup; - -function promise(target, evt, capture) { - let deferred = defer(); - capture = !!capture; - - target.addEventListener(evt, function eventHandler() { - target.removeEventListener(evt, eventHandler, capture); - deferred.resolve(target); - }, capture); - - return deferred.promise; -} -exports.promise = promise; diff --git a/addon-sdk/source/lib/sdk/window/namespace.js b/addon-sdk/source/lib/sdk/window/namespace.js deleted file mode 100644 index b486f888d..000000000 --- a/addon-sdk/source/lib/sdk/window/namespace.js +++ /dev/null @@ -1,6 +0,0 @@ -/* 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"; - -exports.windowNS = require('../core/namespace').ns(); diff --git a/addon-sdk/source/lib/sdk/window/utils.js b/addon-sdk/source/lib/sdk/window/utils.js deleted file mode 100644 index db91a0fed..000000000 --- a/addon-sdk/source/lib/sdk/window/utils.js +++ /dev/null @@ -1,460 +0,0 @@ -/* 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 { Cc, Ci } = require('chrome'); -const array = require('../util/array'); -const { defer } = require('sdk/core/promise'); -const { dispatcher } = require("../util/dispatcher"); - -const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. - getService(Ci.nsIWindowWatcher); -const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. - getService(Ci.nsIAppShellService); -const WM = Cc['@mozilla.org/appshell/window-mediator;1']. - getService(Ci.nsIWindowMediator); -const io = Cc['@mozilla.org/network/io-service;1']. - getService(Ci.nsIIOService); -const FM = Cc["@mozilla.org/focus-manager;1"]. - getService(Ci.nsIFocusManager); - -const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; - -const prefs = require("../preferences/service"); -const BROWSER = 'navigator:browser', - URI_BROWSER = prefs.get('browser.chromeURL', null), - NAME = '_blank', - FEATURES = 'chrome,all,dialog=no,non-private'; - -function isWindowPrivate(win) { - if (!win) - return false; - - // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available, - // and the app is Firefox, then assume per-window private browsing is - // enabled. - try { - return win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsILoadContext) - .usePrivateBrowsing; - } - catch(e) {} - - // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw. - try { - return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing; - } - catch (e) {} - - return false; -} -exports.isWindowPrivate = isWindowPrivate; - -function getMostRecentBrowserWindow() { - return getMostRecentWindow(BROWSER); -} -exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow; - -function getHiddenWindow() { - return appShellService.hiddenDOMWindow; -} -exports.getHiddenWindow = getHiddenWindow; - -function getMostRecentWindow(type) { - return WM.getMostRecentWindow(type); -} -exports.getMostRecentWindow = getMostRecentWindow; - -/** - * Returns the ID of the window's current inner window. - */ -function getInnerId(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; -}; -exports.getInnerId = getInnerId; - -/** - * Returns the ID of the window's outer window. - */ -function getOuterId(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils).outerWindowID; -}; -exports.getOuterId = getOuterId; - -/** - * Returns window by the outer window id. - */ -const getByOuterId = WM.getOuterWindowWithId; -exports.getByOuterId = getByOuterId; - -const getByInnerId = WM.getCurrentInnerWindowWithId; -exports.getByInnerId = getByInnerId; - -/** - * Returns `nsIXULWindow` for the given `nsIDOMWindow`. - */ -function getXULWindow(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIWebNavigation). - QueryInterface(Ci.nsIDocShellTreeItem). - treeOwner.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIXULWindow); -}; -exports.getXULWindow = getXULWindow; - -function getDOMWindow(xulWindow) { - return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindow); -} -exports.getDOMWindow = getDOMWindow; - -/** - * Returns `nsIBaseWindow` for the given `nsIDOMWindow`. - */ -function getBaseWindow(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIWebNavigation). - QueryInterface(Ci.nsIDocShell). - QueryInterface(Ci.nsIDocShellTreeItem). - treeOwner. - QueryInterface(Ci.nsIBaseWindow); -} -exports.getBaseWindow = getBaseWindow; - -/** - * Returns the `nsIDOMWindow` toplevel window for any child/inner window - */ -function getToplevelWindow(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); -} -exports.getToplevelWindow = getToplevelWindow; - -function getWindowDocShell(window) { - return window.gBrowser.docShell; -} -exports.getWindowDocShell = getWindowDocShell; - -function getWindowLoadingContext(window) { - return getWindowDocShell(window). - QueryInterface(Ci.nsILoadContext); -} -exports.getWindowLoadingContext = getWindowLoadingContext; - -const isTopLevel = window => window && getToplevelWindow(window) === window; -exports.isTopLevel = isTopLevel; - -/** - * Takes hash of options and serializes it to a features string that - * can be used passed to `window.open`. For more details on features string see: - * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features - */ -function serializeFeatures(options) { - return Object.keys(options).reduce(function(result, name) { - let value = options[name]; - - // the chrome and private features are special - if ((name == 'private' || name == 'chrome' || name == 'all')) - return result + ((value === true) ? ',' + name : ''); - - return result + ',' + name + '=' + - (value === true ? 'yes' : value === false ? 'no' : value); - }, '').substr(1); -} - -/** - * Opens a top level window and returns it's `nsIDOMWindow` representation. - * @params {String} uri - * URI of the document to be loaded into window. - * @params {nsIDOMWindow} options.parent - * Used as parent for the created window. - * @params {String} options.name - * Optional name that is assigned to the window. - * @params {Object} options.features - * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`. - */ -function open(uri, options) { - uri = uri || URI_BROWSER; - options = options || {}; - - if (!uri) - throw new Error('browser.chromeURL is undefined, please provide an explicit uri'); - - if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0) - throw new Error('only chrome, resource and data uris are allowed'); - - let newWindow = windowWatcher. - openWindow(options.parent || null, - uri, - options.name || null, - options.features ? serializeFeatures(options.features) : null, - options.args || null); - - return newWindow; -} -exports.open = open; - -function onFocus(window) { - let { resolve, promise } = defer(); - - if (isFocused(window)) { - resolve(window); - } - else { - window.addEventListener("focus", function focusListener() { - window.removeEventListener("focus", focusListener, true); - resolve(window); - }, true); - } - - return promise; -} -exports.onFocus = onFocus; - -var isFocused = dispatcher("window-isFocused"); -isFocused.when(x => x instanceof Ci.nsIDOMWindow, (window) => { - const FM = Cc["@mozilla.org/focus-manager;1"]. - getService(Ci.nsIFocusManager); - - let childTargetWindow = {}; - FM.getFocusedElementForWindow(window, true, childTargetWindow); - childTargetWindow = childTargetWindow.value; - - let focusedChildWindow = {}; - if (FM.activeWindow) { - FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow); - focusedChildWindow = focusedChildWindow.value; - } - - return (focusedChildWindow === childTargetWindow); -}); -exports.isFocused = isFocused; - -/** - * Opens a top level window and returns it's `nsIDOMWindow` representation. - * Same as `open` but with more features - * @param {Object} options - * - */ -function openDialog(options) { - options = options || {}; - - let features = options.features || FEATURES; - let featureAry = features.toLowerCase().split(','); - - if (!!options.private) { - // add private flag if private window is desired - if (!array.has(featureAry, 'private')) { - featureAry.push('private'); - } - - // remove the non-private flag ig a private window is desired - let nonPrivateIndex = featureAry.indexOf('non-private'); - if (nonPrivateIndex >= 0) { - featureAry.splice(nonPrivateIndex, 1); - } - - features = featureAry.join(','); - } - - let browser = getMostRecentBrowserWindow(); - - // if there is no browser then do nothing - if (!browser) - return undefined; - - let newWindow = browser.openDialog.apply( - browser, - array.flatten([ - options.url || URI_BROWSER, - options.name || NAME, - features, - options.args || null - ]) - ); - - return newWindow; -} -exports.openDialog = openDialog; - -/** - * Returns an array of all currently opened windows. - * Note that these windows may still be loading. - */ -function windows(type, options) { - options = options || {}; - let list = []; - let winEnum = WM.getEnumerator(type); - while (winEnum.hasMoreElements()) { - let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow); - // Only add non-private windows when pb permission isn't set, - // unless an option forces the addition of them. - if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) { - list.push(window); - } - } - return list; -} -exports.windows = windows; - -/** - * Check if the given window is interactive. - * i.e. if its "DOMContentLoaded" event has already been fired. - * @params {nsIDOMWindow} window - */ -const isInteractive = window => - window.document.readyState === "interactive" || - isDocumentLoaded(window) || - // XUL documents stays '"uninitialized"' until it's `readyState` becomes - // `"complete"`. - isXULDocumentWindow(window) && window.document.readyState === "interactive"; -exports.isInteractive = isInteractive; - -/** - * Check if the given browser window has finished the startup. - * @params {nsIDOMWindow} window - */ -const isStartupFinished = (window) => - isBrowser(window) && - window.gBrowserInit && - window.gBrowserInit.delayedStartupFinished; - -exports.isStartupFinished = isStartupFinished; - -const isXULDocumentWindow = ({document}) => - document.documentElement && - document.documentElement.namespaceURI === XUL_NS; - -/** - * Check if the given window is completely loaded. - * i.e. if its "load" event has already been fired and all possible DOM content - * is done loading (the whole DOM document, images content, ...) - * @params {nsIDOMWindow} window - */ -function isDocumentLoaded(window) { - return window.document.readyState == "complete"; -} -exports.isDocumentLoaded = isDocumentLoaded; - -function isBrowser(window) { - try { - return window.document.documentElement.getAttribute("windowtype") === BROWSER; - } - catch (e) {} - return false; -}; -exports.isBrowser = isBrowser; - -function getWindowTitle(window) { - return window && window.document ? window.document.title : null; -} -exports.getWindowTitle = getWindowTitle; - -function isXULBrowser(window) { - return !!(isBrowser(window) && window.XULBrowserWindow); -} -exports.isXULBrowser = isXULBrowser; - -/** - * Returns the most recent focused window - */ -function getFocusedWindow() { - let window = WM.getMostRecentWindow(BROWSER); - - return window ? window.document.commandDispatcher.focusedWindow : null; -} -exports.getFocusedWindow = getFocusedWindow; - -/** - * Returns the focused browser window if any, or the most recent one. - * Opening new window, updates most recent window, but focus window - * changes later; so most recent window and focused window are not always - * the same. - */ -function getFocusedBrowser() { - let window = FM.activeWindow; - return isBrowser(window) ? window : getMostRecentBrowserWindow() -} -exports.getFocusedBrowser = getFocusedBrowser; - -/** - * Returns the focused element in the most recent focused window - */ -function getFocusedElement() { - let window = WM.getMostRecentWindow(BROWSER); - - return window ? window.document.commandDispatcher.focusedElement : null; -} -exports.getFocusedElement = getFocusedElement; - -function getFrames(window) { - return Array.slice(window.frames).reduce(function(frames, frame) { - return frames.concat(frame, getFrames(frame)); - }, []); -} -exports.getFrames = getFrames; - -function getScreenPixelsPerCSSPixel(window) { - return window.QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel; -} -exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel; - -function getOwnerBrowserWindow(node) { - /** - Takes DOM node and returns browser window that contains it. - **/ - let window = getToplevelWindow(node.ownerDocument.defaultView); - // If anchored window is browser then it's target browser window. - return isBrowser(window) ? window : null; -} -exports.getOwnerBrowserWindow = getOwnerBrowserWindow; - -function getParentWindow(window) { - try { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem).parent - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } - catch (e) {} - return null; -} -exports.getParentWindow = getParentWindow; - - -function getParentFrame(window) { - try { - return window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem).parent - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } - catch (e) {} - return null; -} -exports.getParentWindow = getParentWindow; - -// The element in which the window is embedded, or `null` -// if the window is top-level. Similar to `window.frameElement` -// but can cross chrome-content boundries. -const getFrameElement = target => - (target instanceof Ci.nsIDOMDocument ? target.defaultView : target). - QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils). - containerElement; -exports.getFrameElement = getFrameElement; diff --git a/addon-sdk/source/lib/sdk/windows.js b/addon-sdk/source/lib/sdk/windows.js deleted file mode 100644 index 06dbe70b2..000000000 --- a/addon-sdk/source/lib/sdk/windows.js +++ /dev/null @@ -1,32 +0,0 @@ -/* 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': 'stable' -}; - -const { isBrowser } = require('./window/utils'); -const { modelFor } = require('./model/core'); -const { viewFor } = require('./view/core'); - - -if (require('./system/xul-app').is('Fennec')) { - module.exports = require('./windows/fennec'); -} -else { - module.exports = require('./windows/firefox'); -} - - -const browsers = module.exports.browserWindows; - -// -modelFor.when(isBrowser, view => { - for (let model of browsers) { - if (viewFor(model) === view) - return model; - } - return null; -}); diff --git a/addon-sdk/source/lib/sdk/windows/fennec.js b/addon-sdk/source/lib/sdk/windows/fennec.js deleted file mode 100644 index 3c3b6c313..000000000 --- a/addon-sdk/source/lib/sdk/windows/fennec.js +++ /dev/null @@ -1,83 +0,0 @@ -/* 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/addon-sdk/source/lib/sdk/windows/firefox.js b/addon-sdk/source/lib/sdk/windows/firefox.js deleted file mode 100644 index 1eb1d8488..000000000 --- a/addon-sdk/source/lib/sdk/windows/firefox.js +++ /dev/null @@ -1,224 +0,0 @@ -/* 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/addon-sdk/source/lib/sdk/windows/observer.js b/addon-sdk/source/lib/sdk/windows/observer.js deleted file mode 100644 index 5ba2535f1..000000000 --- a/addon-sdk/source/lib/sdk/windows/observer.js +++ /dev/null @@ -1,53 +0,0 @@ -/* 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/addon-sdk/source/lib/sdk/windows/tabs-fennec.js b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js deleted file mode 100644 index 0ef5ec9f5..000000000 --- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js +++ /dev/null @@ -1,172 +0,0 @@ -/* 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); -} diff --git a/addon-sdk/source/lib/sdk/worker/utils.js b/addon-sdk/source/lib/sdk/worker/utils.js deleted file mode 100644 index fca19be63..000000000 --- a/addon-sdk/source/lib/sdk/worker/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -/* 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': 'deprecated' -}; - -const { - requiresAddonGlobal, attach, detach, destroy, WorkerHost -} = require('../content/utils'); - -exports.WorkerHost = WorkerHost; -exports.detach = detach; -exports.attach = attach; -exports.destroy = destroy; -exports.requiresAddonGlobal = requiresAddonGlobal; diff --git a/addon-sdk/source/lib/sdk/zip/utils.js b/addon-sdk/source/lib/sdk/zip/utils.js deleted file mode 100644 index e600380cb..000000000 --- a/addon-sdk/source/lib/sdk/zip/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -/* 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"); - -function getZipReader(aFile) { - return new Promise(resolve => { - let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - zipReader.open(aFile); - resolve(zipReader); - }); -}; -exports.getZipReader = getZipReader; |