/* 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;