From ac46df8daea09899ce30dc8fd70986e258c746bf Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 9 Feb 2018 06:46:43 -0500 Subject: Move Add-on SDK source to toolkit/jetpack --- toolkit/jetpack/sdk/dom/events-shimmed.js | 18 +++ toolkit/jetpack/sdk/dom/events.js | 192 ++++++++++++++++++++++++++++++ toolkit/jetpack/sdk/dom/events/keys.js | 63 ++++++++++ 3 files changed, 273 insertions(+) create mode 100644 toolkit/jetpack/sdk/dom/events-shimmed.js create mode 100644 toolkit/jetpack/sdk/dom/events.js create mode 100644 toolkit/jetpack/sdk/dom/events/keys.js (limited to 'toolkit/jetpack/sdk/dom') diff --git a/toolkit/jetpack/sdk/dom/events-shimmed.js b/toolkit/jetpack/sdk/dom/events-shimmed.js new file mode 100644 index 000000000..7a1727681 --- /dev/null +++ b/toolkit/jetpack/sdk/dom/events-shimmed.js @@ -0,0 +1,18 @@ +/* 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/toolkit/jetpack/sdk/dom/events.js b/toolkit/jetpack/sdk/dom/events.js new file mode 100644 index 000000000..502d2350f --- /dev/null +++ b/toolkit/jetpack/sdk/dom/events.js @@ -0,0 +1,192 @@ +/* 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/toolkit/jetpack/sdk/dom/events/keys.js b/toolkit/jetpack/sdk/dom/events/keys.js new file mode 100644 index 000000000..e6f1483a2 --- /dev/null +++ b/toolkit/jetpack/sdk/dom/events/keys.js @@ -0,0 +1,63 @@ +/* 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)); +}; + -- cgit v1.2.3