forked from mirrors/gecko-dev
		
	dom.js and listener.js pass around `target` pointing to an event target, using CPOWs, but the implementation of WebElementEventTarget then always overrides that with `this` anyway, so there's no point. This change removes the handling of objects/target. Differential Revision: https://phabricator.services.mozilla.com/D71513
		
			
				
	
	
		
			213 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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 { XPCOMUtils } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/XPCOMUtils.jsm"
 | 
						|
);
 | 
						|
 | 
						|
const { Log } = ChromeUtils.import("chrome://marionette/content/log.js");
 | 
						|
 | 
						|
XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
 | 
						|
 | 
						|
this.EXPORTED_SYMBOLS = [
 | 
						|
  "ContentEventObserverService",
 | 
						|
  "WebElementEventTarget",
 | 
						|
];
 | 
						|
 | 
						|
/**
 | 
						|
 * The ``EventTarget`` for web elements can be used to observe DOM
 | 
						|
 * events in the content document.
 | 
						|
 *
 | 
						|
 * A caveat of the current implementation is that it is only possible
 | 
						|
 * to listen for top-level ``window`` global events.
 | 
						|
 *
 | 
						|
 * It needs to be backed by a :js:class:`ContentEventObserverService`
 | 
						|
 * in a content frame script.
 | 
						|
 *
 | 
						|
 * Usage::
 | 
						|
 *
 | 
						|
 *     let observer = new WebElementEventTarget(messageManager);
 | 
						|
 *     await new Promise(resolve => {
 | 
						|
 *       observer.addEventListener("visibilitychange", resolve, {once: true});
 | 
						|
 *       chromeWindow.minimize();
 | 
						|
 *     });
 | 
						|
 */
 | 
						|
class WebElementEventTarget {
 | 
						|
  /**
 | 
						|
   * @param {function(): nsIMessageListenerManager} messageManagerFn
 | 
						|
   *     Message manager to the current browser.
 | 
						|
   */
 | 
						|
  constructor(messageManager) {
 | 
						|
    this.mm = messageManager;
 | 
						|
    this.listeners = {};
 | 
						|
    this.mm.addMessageListener("Marionette:DOM:OnEvent", this);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Register an event handler of a specific event type from the content
 | 
						|
   * frame.
 | 
						|
   *
 | 
						|
   * @param {string} type
 | 
						|
   *     Event type to listen for.
 | 
						|
   * @param {EventListener} listener
 | 
						|
   *     Object which receives a notification (a ``BareEvent``)
 | 
						|
   *     when an event of the specified type occurs.  This must be
 | 
						|
   *     an object implementing the ``EventListener`` interface,
 | 
						|
   *     or a JavaScript function.
 | 
						|
   * @param {boolean=} once
 | 
						|
   *     Indicates that the ``listener`` should be invoked at
 | 
						|
   *     most once after being added.  If true, the ``listener``
 | 
						|
   *     would automatically be removed when invoked.
 | 
						|
   */
 | 
						|
  addEventListener(type, listener, { once = false } = {}) {
 | 
						|
    if (!(type in this.listeners)) {
 | 
						|
      this.listeners[type] = [];
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this.listeners[type].includes(listener)) {
 | 
						|
      listener.once = once;
 | 
						|
      this.listeners[type].push(listener);
 | 
						|
    }
 | 
						|
 | 
						|
    this.mm.sendAsyncMessage("Marionette:DOM:AddEventListener", { type });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes an event listener.
 | 
						|
   *
 | 
						|
   * @param {string} type
 | 
						|
   *     Type of event to cease listening for.
 | 
						|
   * @param {EventListener} listener
 | 
						|
   *     Event handler to remove from the event target.
 | 
						|
   */
 | 
						|
  removeEventListener(type, listener) {
 | 
						|
    if (!(type in this.listeners)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let stack = this.listeners[type];
 | 
						|
    for (let i = stack.length - 1; i >= 0; --i) {
 | 
						|
      if (stack[i] === listener) {
 | 
						|
        stack.splice(i, 1);
 | 
						|
        if (stack.length == 0) {
 | 
						|
          this.mm.sendAsyncMessage("Marionette:DOM:RemoveEventListener", {
 | 
						|
            type,
 | 
						|
          });
 | 
						|
        }
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  dispatchEvent(event) {
 | 
						|
    if (!(event.type in this.listeners)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    event.target = this;
 | 
						|
 | 
						|
    let stack = this.listeners[event.type].slice(0);
 | 
						|
    stack.forEach(listener => {
 | 
						|
      if (typeof listener.handleEvent == "function") {
 | 
						|
        listener.handleEvent(event);
 | 
						|
      } else {
 | 
						|
        listener(event);
 | 
						|
      }
 | 
						|
 | 
						|
      if (listener.once) {
 | 
						|
        this.removeEventListener(event.type, listener);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  receiveMessage({ name, data }) {
 | 
						|
    if (name != "Marionette:DOM:OnEvent") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let ev = {
 | 
						|
      type: data.type,
 | 
						|
    };
 | 
						|
    this.dispatchEvent(ev);
 | 
						|
  }
 | 
						|
}
 | 
						|
this.WebElementEventTarget = WebElementEventTarget;
 | 
						|
 | 
						|
/**
 | 
						|
 * Provides the frame script backend for the
 | 
						|
 * :js:class:`WebElementEventTarget`.
 | 
						|
 *
 | 
						|
 * This service receives requests for new DOM events to listen for and
 | 
						|
 * to cease listening for, and despatches IPC messages to the browser
 | 
						|
 * when they fire.
 | 
						|
 */
 | 
						|
class ContentEventObserverService {
 | 
						|
  /**
 | 
						|
   * @param {WindowProxy} windowGlobal
 | 
						|
   *     Window.
 | 
						|
   * @param {nsIMessageSender.sendAsyncMessage} sendAsyncMessage
 | 
						|
   *     Function for sending an async message to the parent browser.
 | 
						|
   */
 | 
						|
  constructor(windowGlobal, sendAsyncMessage) {
 | 
						|
    this.window = windowGlobal;
 | 
						|
    this.sendAsyncMessage = sendAsyncMessage;
 | 
						|
    this.events = new Set();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Observe a new DOM event.
 | 
						|
   *
 | 
						|
   * When the DOM event of ``type`` fires, a message is passed to
 | 
						|
   * the parent browser's event observer.
 | 
						|
   *
 | 
						|
   * If event type is already being observed, only a single message
 | 
						|
   * is sent.  E.g. multiple registration for events will only ever emit
 | 
						|
   * a maximum of one message.
 | 
						|
   *
 | 
						|
   * @param {string} type
 | 
						|
   *     DOM event to listen for.
 | 
						|
   */
 | 
						|
  add(type) {
 | 
						|
    if (this.events.has(type)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.window.addEventListener(type, this);
 | 
						|
    this.events.add(type);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Ceases observing a DOM event.
 | 
						|
   *
 | 
						|
   * @param {string} type
 | 
						|
   *     DOM event to stop listening for.
 | 
						|
   */
 | 
						|
  remove(type) {
 | 
						|
    if (!this.events.has(type)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.window.removeEventListener(type, this);
 | 
						|
    this.events.delete(type);
 | 
						|
  }
 | 
						|
 | 
						|
  /** Ceases observing all previously registered DOM events. */
 | 
						|
  clear() {
 | 
						|
    for (let ev of this) {
 | 
						|
      this.remove(ev);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  *[Symbol.iterator]() {
 | 
						|
    for (let ev of this.events) {
 | 
						|
      yield ev;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  handleEvent({ type, target }) {
 | 
						|
    logger.trace(`Received DOM event ${type}`);
 | 
						|
    this.sendAsyncMessage("Marionette:DOM:OnEvent", { type });
 | 
						|
  }
 | 
						|
}
 | 
						|
this.ContentEventObserverService = ContentEventObserverService;
 |