123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- /* Copyright 2012 Mozilla Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- const WaitOnType = {
- EVENT: "event",
- TIMEOUT: "timeout",
- };
- /**
- * @typedef {Object} WaitOnEventOrTimeoutParameters
- * @property {Object} target - The event target, can for example be:
- * `window`, `document`, a DOM element, or an {EventBus} instance.
- * @property {string} name - The name of the event.
- * @property {number} delay - The delay, in milliseconds, after which the
- * timeout occurs (if the event wasn't already dispatched).
- */
- /**
- * Allows waiting for an event or a timeout, whichever occurs first.
- * Can be used to ensure that an action always occurs, even when an event
- * arrives late or not at all.
- *
- * @param {WaitOnEventOrTimeoutParameters}
- * @returns {Promise} A promise that is resolved with a {WaitOnType} value.
- */
- function waitOnEventOrTimeout({ target, name, delay = 0 }) {
- return new Promise(function (resolve, reject) {
- if (
- typeof target !== "object" ||
- !(name && typeof name === "string") ||
- !(Number.isInteger(delay) && delay >= 0)
- ) {
- throw new Error("waitOnEventOrTimeout - invalid parameters.");
- }
- function handler(type) {
- if (target instanceof EventBus) {
- target._off(name, eventHandler);
- } else {
- target.removeEventListener(name, eventHandler);
- }
- if (timeout) {
- clearTimeout(timeout);
- }
- resolve(type);
- }
- const eventHandler = handler.bind(null, WaitOnType.EVENT);
- if (target instanceof EventBus) {
- target._on(name, eventHandler);
- } else {
- target.addEventListener(name, eventHandler);
- }
- const timeoutHandler = handler.bind(null, WaitOnType.TIMEOUT);
- const timeout = setTimeout(timeoutHandler, delay);
- });
- }
- /**
- * Simple event bus for an application. Listeners are attached using the `on`
- * and `off` methods. To raise an event, the `dispatch` method shall be used.
- */
- class EventBus {
- #listeners = Object.create(null);
- /**
- * @param {string} eventName
- * @param {function} listener
- * @param {Object} [options]
- */
- on(eventName, listener, options = null) {
- this._on(eventName, listener, {
- external: true,
- once: options?.once,
- });
- }
- /**
- * @param {string} eventName
- * @param {function} listener
- * @param {Object} [options]
- */
- off(eventName, listener, options = null) {
- this._off(eventName, listener, {
- external: true,
- once: options?.once,
- });
- }
- /**
- * @param {string} eventName
- * @param {Object} data
- */
- dispatch(eventName, data) {
- const eventListeners = this.#listeners[eventName];
- if (!eventListeners || eventListeners.length === 0) {
- return;
- }
- let externalListeners;
- // Making copy of the listeners array in case if it will be modified
- // during dispatch.
- for (const { listener, external, once } of eventListeners.slice(0)) {
- if (once) {
- this._off(eventName, listener);
- }
- if (external) {
- (externalListeners ||= []).push(listener);
- continue;
- }
- listener(data);
- }
- // Dispatch any "external" listeners *after* the internal ones, to give the
- // viewer components time to handle events and update their state first.
- if (externalListeners) {
- for (const listener of externalListeners) {
- listener(data);
- }
- externalListeners = null;
- }
- }
- /**
- * @ignore
- */
- _on(eventName, listener, options = null) {
- const eventListeners = (this.#listeners[eventName] ||= []);
- eventListeners.push({
- listener,
- external: options?.external === true,
- once: options?.once === true,
- });
- }
- /**
- * @ignore
- */
- _off(eventName, listener, options = null) {
- const eventListeners = this.#listeners[eventName];
- if (!eventListeners) {
- return;
- }
- for (let i = 0, ii = eventListeners.length; i < ii; i++) {
- if (eventListeners[i].listener === listener) {
- eventListeners.splice(i, 1);
- return;
- }
- }
- }
- }
- /**
- * NOTE: Only used to support various PDF viewer tests in `mozilla-central`.
- */
- class AutomationEventBus extends EventBus {
- dispatch(eventName, data) {
- if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("MOZCENTRAL")) {
- throw new Error("Not implemented: AutomationEventBus.dispatch");
- }
- super.dispatch(eventName, data);
- const details = Object.create(null);
- if (data) {
- for (const key in data) {
- const value = data[key];
- if (key === "source") {
- if (value === window || value === document) {
- return; // No need to re-dispatch (already) global events.
- }
- continue; // Ignore the `source` property.
- }
- details[key] = value;
- }
- }
- const event = document.createEvent("CustomEvent");
- event.initCustomEvent(eventName, true, true, details);
- document.dispatchEvent(event);
- }
- }
- export { AutomationEventBus, EventBus, waitOnEventOrTimeout, WaitOnType };
|