Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  event-collector.js   Sprache: JAVA

 
/* 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 contains event collectors that are then used by developer tools in
// order to find information about events affecting an HTML element.

"use strict";

const {
  isAfterPseudoElement,
  isBeforePseudoElement,
  isMarkerPseudoElement,
  isNativeAnonymous,
} = require("resource://devtools/shared/layout/utils.js");
const Debugger = require("Debugger");
const {
  EXCLUDED_LISTENER,
} = require("resource://devtools/server/actors/inspector/constants.js");

// eslint-disable-next-line
const JQUERY_LIVE_REGEX =
  /return typeof \w+.*.event\.triggered[\s\S]*\.event\.(dispatch|handle).*arguments/;

const REACT_EVENT_NAMES = [
  "onAbort",
  "onAnimationEnd",
  "onAnimationIteration",
  "onAnimationStart",
  "onAuxClick",
  "onBeforeInput",
  "onBlur",
  "onCanPlay",
  "onCanPlayThrough",
  "onCancel",
  "onChange",
  "onClick",
  "onClose",
  "onCompositionEnd",
  "onCompositionStart",
  "onCompositionUpdate",
  "onContextMenu",
  "onCopy",
  "onCut",
  "onDoubleClick",
  "onDrag",
  "onDragEnd",
  "onDragEnter",
  "onDragExit",
  "onDragLeave",
  "onDragOver",
  "onDragStart",
  "onDrop",
  "onDurationChange",
  "onEmptied",
  "onEncrypted",
  "onEnded",
  "onError",
  "onFocus",
  "onGotPointerCapture",
  "onInput",
  "onInvalid",
  "onKeyDown",
  "onKeyPress",
  "onKeyUp",
  "onLoad",
  "onLoadStart",
  "onLoadedData",
  "onLoadedMetadata",
  "onLostPointerCapture",
  "onMouseDown",
  "onMouseEnter",
  "onMouseLeave",
  "onMouseMove",
  "onMouseOut",
  "onMouseOver",
  "onMouseUp",
  "onPaste",
  "onPause",
  "onPlay",
  "onPlaying",
  "onPointerCancel",
  "onPointerDown",
  "onPointerEnter",
  "onPointerLeave",
  "onPointerMove",
  "onPointerOut",
  "onPointerOver",
  "onPointerUp",
  "onProgress",
  "onRateChange",
  "onReset",
  "onScroll",
  "onSeeked",
  "onSeeking",
  "onSelect",
  "onStalled",
  "onSubmit",
  "onSuspend",
  "onTimeUpdate",
  "onToggle",
  "onTouchCancel",
  "onTouchEnd",
  "onTouchMove",
  "onTouchStart",
  "onTransitionEnd",
  "onVolumeChange",
  "onWaiting",
  "onWheel",
  "onAbortCapture",
  "onAnimationEndCapture",
  "onAnimationIterationCapture",
  "onAnimationStartCapture",
  "onAuxClickCapture",
  "onBeforeInputCapture",
  "onBlurCapture",
  "onCanPlayCapture",
  "onCanPlayThroughCapture",
  "onCancelCapture",
  "onChangeCapture",
  "onClickCapture",
  "onCloseCapture",
  "onCompositionEndCapture",
  "onCompositionStartCapture",
  "onCompositionUpdateCapture",
  "onContextMenuCapture",
  "onCopyCapture",
  "onCutCapture",
  "onDoubleClickCapture",
  "onDragCapture",
  "onDragEndCapture",
  "onDragEnterCapture",
  "onDragExitCapture",
  "onDragLeaveCapture",
  "onDragOverCapture",
  "onDragStartCapture",
  "onDropCapture",
  "onDurationChangeCapture",
  "onEmptiedCapture",
  "onEncryptedCapture",
  "onEndedCapture",
  "onErrorCapture",
  "onFocusCapture",
  "onGotPointerCaptureCapture",
  "onInputCapture",
  "onInvalidCapture",
  "onKeyDownCapture",
  "onKeyPressCapture",
  "onKeyUpCapture",
  "onLoadCapture",
  "onLoadStartCapture",
  "onLoadedDataCapture",
  "onLoadedMetadataCapture",
  "onLostPointerCaptureCapture",
  "onMouseDownCapture",
  "onMouseEnterCapture",
  "onMouseLeaveCapture",
  "onMouseMoveCapture",
  "onMouseOutCapture",
  "onMouseOverCapture",
  "onMouseUpCapture",
  "onPasteCapture",
  "onPauseCapture",
  "onPlayCapture",
  "onPlayingCapture",
  "onPointerCancelCapture",
  "onPointerDownCapture",
  "onPointerEnterCapture",
  "onPointerLeaveCapture",
  "onPointerMoveCapture",
  "onPointerOutCapture",
  "onPointerOverCapture",
  "onPointerUpCapture",
  "onProgressCapture",
  "onRateChangeCapture",
  "onResetCapture",
  "onScrollCapture",
  "onSeekedCapture",
  "onSeekingCapture",
  "onSelectCapture",
  "onStalledCapture",
  "onSubmitCapture",
  "onSuspendCapture",
  "onTimeUpdateCapture",
  "onToggleCapture",
  "onTouchCancelCapture",
  "onTouchEndCapture",
  "onTouchMoveCapture",
  "onTouchStartCapture",
  "onTransitionEndCapture",
  "onVolumeChangeCapture",
  "onWaitingCapture",
  "onWheelCapture",
];

/**
 * The base class that all the enent collectors should be based upon.
 */

class MainEventCollector {
  /**
   * We allow displaying chrome events if the page is chrome or if
   * `devtools.chrome.enabled = true`.
   */

  get chromeEnabled() {
    if (typeof this._chromeEnabled === "undefined") {
      this._chromeEnabled = Services.prefs.getBoolPref(
        "devtools.chrome.enabled"
      );
    }

    return this._chromeEnabled;
  }

  /**
   * Check if a node has any event listeners attached. Please do not override
   * this method... your getListeners() implementation needs to have the
   * following signature:
   * `getListeners(node, {checkOnly} = {})`
   *
   * @param  {DOMNode} node
   *         The not for which we want to check for event listeners.
   * @return {Boolean}
   *         true if the node has event listeners, false otherwise.
   */

  hasListeners(node) {
    return this.getListeners(node, {
      checkOnly: true,
    });
  }

  /**
   * Get all listeners for a node. This method must be overridden.
   *
   * @param  {DOMNode} node
   *         The not for which we want to get event listeners.
   * @param  {Object} options
   *         An object for passing in options.
   * @param  {Boolean} [options.checkOnly = false]
   *         Don't get any listeners but return true when the first event is
   *         found.
   * @return {Array}
   *         An array of event handlers.
   */

  getListeners(_node, { checkOnly: _checkOnly }) {
    throw new Error("You have to implement the method getListeners()!");
  }

  /**
   * Get unfiltered DOM Event listeners for a node.
   * NOTE: These listeners may contain invalid events and events based
   *       on C++ rather than JavaScript.
   *
   * @param  {DOMNode} node
   *         The node for which we want to get unfiltered event listeners.
   * @return {Array}
   *         An array of unfiltered event listeners or an empty array
   */

  getDOMListeners(node) {
    const listeners = [];
    const listenersTargets = [];

    if (
      typeof node.nodeName !== "undefined" &&
      node.nodeName.toLowerCase() === "html"
    ) {
      listenersTargets.push(node.ownerGlobal, node, node.parentNode);
    } else {
      listenersTargets.push(node);
    }

    for (const el of listenersTargets) {
      const elListeners = Services.els.getListenerInfoFor(el);
      if (!elListeners) {
        continue;
      }
      for (const listener of elListeners) {
        const obj = this.unwrap(listener.listenerObject);
        if (!obj || !obj[EXCLUDED_LISTENER]) {
          listeners.push(listener);
        }
      }
    }

    return listeners;
  }

  getJQuery(node) {
    if (Cu.isDeadWrapper(node)) {
      return null;
    }

    const global = this.unwrap(node.ownerGlobal);
    if (!global) {
      return null;
    }

    const hasJQuery = global.jQuery?.fn?.jquery;

    if (hasJQuery) {
      return global.jQuery;
    }
    return null;
  }

  unwrap(obj) {
    return Cu.isXrayWrapper(obj) ? obj.wrappedJSObject : obj;
  }

  isChromeHandler(handler) {
    try {
      const handlerPrincipal = Cu.getObjectPrincipal(handler);

      // Chrome codebase may register listeners on the page from a frame script or
      // JSM <video> tags may also report internal listeners, but they won't be
      // coming from the system principal. Instead, they will be using an expanded
      // principal.
      return (
        handlerPrincipal.isSystemPrincipal ||
        handlerPrincipal.isExpandedPrincipal
      );
    } catch (e) {
      // Anything from a dead object to a CSP error can leave us here so let's
      // return false so that we can fail gracefully.
      return false;
    }
  }
}

/**
 * Get or detect DOM events. These may include DOM events created by libraries
 * that enable their custom events to work. At this point we are unable to
 * effectively filter them as they may be proxied or wrapped. Although we know
 * there is an event, we may not know the true contents until it goes
 * through `processHandlerForEvent()`.
 */

class DOMEventCollector extends MainEventCollector {
  getListeners(node, { checkOnly } = {}) {
    const handlers = [];
    const listeners = this.getDOMListeners(node);

    for (const listener of listeners) {
      // Ignore listeners without a type, e.g.
      // node.addEventListener("", function() {})
      if (!listener.type) {
        continue;
      }

      // Get the listener object, either a Function or an Object.
      const obj = listener.listenerObject;

      // Ignore listeners without any listener, e.g.
      // node.addEventListener("mouseover", null);
      if (!obj) {
        continue;
      }

      let handler = null;

      // An object without a valid handleEvent is not a valid listener.
      if (typeof obj === "object") {
        const unwrapped = this.unwrap(obj);
        if (typeof unwrapped.handleEvent === "function") {
          handler = Cu.unwaiveXrays(unwrapped.handleEvent);
        }
      } else if (typeof obj === "function") {
        // Ignore DOM events used to trigger jQuery events as they are only
        // useful to the developers of the jQuery library.
        if (JQUERY_LIVE_REGEX.test(obj.toString())) {
          continue;
        }
        // Otherwise, the other valid listener type is function.
        handler = obj;
      }

      // Ignore listeners that have no handler.
      if (!handler) {
        continue;
      }

      // If we shouldn't be showing chrome events due to context and this is a
      // chrome handler we can ignore it.
      if (!this.chromeEnabled && this.isChromeHandler(handler)) {
        continue;
      }

      // If this is checking if a node has any listeners then we have found one
      // so return now.
      if (checkOnly) {
        return true;
      }

      const eventInfo = {
        nsIEventListenerInfo: listener,
        capturing: listener.capturing,
        type: listener.type,
        handler,
        enabled: listener.enabled,
      };

      handlers.push(eventInfo);
    }

    // If this is checking if a node has any listeners then none were found so
    // return false.
    if (checkOnly) {
      return false;
    }

    return handlers;
  }
}

/**
 * Get or detect jQuery events.
 */

class JQueryEventCollector extends MainEventCollector {
  // eslint-disable-next-line complexity
  getListeners(node, { checkOnly } = {}) {
    const jQuery = this.getJQuery(node);
    const handlers = [];

    // If jQuery is not on the page, if this is an anonymous node or a pseudo
    // element we need to return early.
    if (
      !jQuery ||
      isNativeAnonymous(node) ||
      isMarkerPseudoElement(node) ||
      isBeforePseudoElement(node) ||
      isAfterPseudoElement(node)
    ) {
      if (checkOnly) {
        return false;
      }
      return handlers;
    }

    let eventsObj = null;
    const data = jQuery._data || jQuery.data;

    if (data) {
      // jQuery 1.2+
      try {
        eventsObj = data(node, "events");
      } catch (e) {
        // We have no access to a JS object. This is probably due to a CORS
        // violation. Using try / catch is the only way to avoid this error.
      }
    } else {
      // JQuery 1.0 & 1.1
      let entry;
      try {
        entry = entry = jQuery(node)[0];
      } catch (e) {
        // We have no access to a JS object. This is probably due to a CORS
        // violation. Using try / catch is the only way to avoid this error.
      }

      if (!entry || !entry.events) {
        if (checkOnly) {
          return false;
        }
        return handlers;
      }

      eventsObj = entry.events;
    }

    if (eventsObj) {
      for (const type in eventsObj) {
        let events = eventsObj[type];
        // We can get arrays or objects. When we get the latter,
        // the events are the object values.
        if (!Array.isArray(events)) {
          events = Object.values(events);
        }
        for (const event of events) {
          // Skip events that are part of jQueries internals.
          if (node.nodeType == node.DOCUMENT_NODE && event.selector) {
            continue;
          }

          if (typeof event === "function" || typeof event === "object") {
            // If we shouldn't be showing chrome events due to context and this
            // is a chrome handler we can ignore it.
            const handler = event.handler || event;
            if (!this.chromeEnabled && this.isChromeHandler(handler)) {
              continue;
            }

            if (checkOnly) {
              return true;
            }

            const eventInfo = {
              type,
              handler,
              tags: "jQuery",
              hide: {
                capturing: true,
              },
            };

            handlers.push(eventInfo);
          }
        }
      }
    }

    if (checkOnly) {
      return false;
    }
    return handlers;
  }
}

/**
 * Get or detect jQuery live events.
 */

class JQueryLiveEventCollector extends MainEventCollector {
  // eslint-disable-next-line complexity
  getListeners(node, { checkOnly } = {}) {
    const jQuery = this.getJQuery(node);
    const handlers = [];

    if (!jQuery) {
      if (checkOnly) {
        return false;
      }
      return handlers;
    }

    const jqueryData = jQuery._data || jQuery.data;

    if (jqueryData) {
      // Live events are added to the document and bubble up to all elements.
      // Any element matching the specified selector will trigger the live
      // event.
      const win = this.unwrap(node.ownerGlobal);
      let events = null;

      try {
        events = jqueryData(win.document, "events");
      } catch (e) {
        // We have no access to a JS object. This is probably due to a CORS
        // violation. Using try / catch is the only way to avoid this error.
      }

      if (events && node.ownerDocument && node.matches) {
        for (const eventName in events) {
          const eventHolder = events[eventName];
          for (const idx in eventHolder) {
            if (typeof idx !== "string" || isNaN(parseInt(idx, 10))) {
              continue;
            }

            const event = eventHolder[idx];
            let { selector, data } = event;

            if (!selector && data) {
              selector = data.selector || data;
            }

            if (!selector) {
              continue;
            }

            let matches;
            try {
              matches = node.matches(selector);
            } catch (e) {
              // Invalid selector, do nothing.
            }

            if (!matches) {
              continue;
            }

            if (typeof event === "function" || typeof event === "object") {
              // If we shouldn't be showing chrome events due to context and this
              // is a chrome handler we can ignore it.
              const handler = event.handler || event;
              if (!this.chromeEnabled && this.isChromeHandler(handler)) {
                continue;
              }

              if (checkOnly) {
                return true;
              }
              const eventInfo = {
                type: event.origType || event.type.substr(selector.length + 1),
                handler,
                tags: "jQuery,Live",
                hide: {
                  capturing: true,
                },
              };

              if (!eventInfo.type && data?.live) {
                eventInfo.type = event.data.live;
              }

              handlers.push(eventInfo);
            }
          }
        }
      }
    }

    if (checkOnly) {
      return false;
    }
    return handlers;
  }

  normalizeListener(handlerDO) {
    function isFunctionInProxy(funcDO) {
      // If the anonymous function is inside the |proxy| function and the
      // function only has guessed atom, the guessed atom should starts with
      // "proxy/".
      const displayName = funcDO.displayName;
      if (displayName && displayName.startsWith("proxy/")) {
        return true;
      }

      // If the anonymous function is inside the |proxy| function and the
      // function gets name at compile time by SetFunctionName, its guessed
      // atom doesn't contain "proxy/".  In that case, check if the caller is
      // "proxy" function, as a fallback.
      const calleeDS = funcDO.environment?.calleeScript;
      if (!calleeDS) {
        return false;
      }
      const calleeName = calleeDS.displayName;
      return calleeName == "proxy";
    }

    function getFirstFunctionVariable(funcDO) {
      // The handler function inside the |proxy| function should point the
      // unwrapped function via environment variable.
      const names = funcDO.environment ? funcDO.environment.names() : [];
      for (const varName of names) {
        const varDO = handlerDO.environment
          ? handlerDO.environment.getVariable(varName)
          : null;
        if (!varDO) {
          continue;
        }
        if (varDO.class == "Function") {
          return varDO;
        }
      }
      return null;
    }

    if (!isFunctionInProxy(handlerDO)) {
      return handlerDO;
    }

    const MAX_NESTED_HANDLER_COUNT = 2;
    for (let i = 0; i < MAX_NESTED_HANDLER_COUNT; i++) {
      const funcDO = getFirstFunctionVariable(handlerDO);
      if (!funcDO) {
        return handlerDO;
      }

      handlerDO = funcDO;
      if (isFunctionInProxy(handlerDO)) {
        continue;
      }
      break;
    }

    return handlerDO;
  }
}

/**
 * Get or detect React events.
 */

class ReactEventCollector extends MainEventCollector {
  getListeners(node, { checkOnly } = {}) {
    const handlers = [];
    const props = this.getProps(node);

    if (props) {
      for (const [name, prop] of Object.entries(props)) {
        if (REACT_EVENT_NAMES.includes(name)) {
          const listener = prop?.__reactBoundMethod || prop;

          if (typeof listener !== "function") {
            continue;
          }

          if (!this.chromeEnabled && this.isChromeHandler(listener)) {
            continue;
          }

          if (checkOnly) {
            return true;
          }

          const handler = {
            type: name,
            handler: listener,
            tags: "React",
            override: {
              capturing: name.endsWith("Capture"),
            },
          };

          handlers.push(handler);
        }
      }
    }

    if (checkOnly) {
      return false;
    }

    return handlers;
  }

  getProps(node) {
    node = this.unwrap(node);

    for (const key of Object.keys(node)) {
      if (key.startsWith("__reactInternalInstance$")) {
        const value = node[key];
        if (value.memoizedProps) {
          return value.memoizedProps; // React 16
        }
        return value?._currentElement?.props; // React 15
      }
    }
    return null;
  }

  normalizeListener(handlerDO, listener) {
    let functionText = "";

    if (handlerDO.boundTargetFunction) {
      handlerDO = handlerDO.boundTargetFunction;
    }

    const script = handlerDO.script;
    // Script might be undefined (eg for methods bound several times, see
    // https://bugzilla.mozilla.org/show_bug.cgi?id=1589658)
    const introScript = script?.source.introductionScript;

    // If this is a Babel transpiled function we have no access to the
    // source location so we need to hide the filename and debugger
    // icon.
    if (introScript && introScript.displayName.endsWith("/transform.run")) {
      listener.hide.debugger = true;
      listener.hide.filename = true;

      if (!handlerDO.isArrowFunction) {
        functionText += "function (";
      } else {
        functionText += "(";
      }

      functionText += handlerDO.parameterNames.join(", ");

      functionText += ") {\n";

      const scriptSource = script.source.text;
      functionText += scriptSource.substr(
        script.sourceStart,
        script.sourceLength
      );

      listener.override.handler = functionText;
    }

    return handlerDO;
  }
}

/**
 * The exposed class responsible for gathering events.
 */

class EventCollector {
  constructor(targetActor) {
    this.targetActor = targetActor;

    // The event collector array. Please preserve the order otherwise there will
    // be multiple failing tests.
    this.eventCollectors = [
      new ReactEventCollector(),
      new JQueryLiveEventCollector(),
      new JQueryEventCollector(),
      new DOMEventCollector(),
    ];
  }

  /**
   * Destructor (must be called manually).
   */

  destroy() {
    this.eventCollectors = null;
  }

  /**
   * Iterate through all event collectors returning on the first found event.
   *
   * @param  {DOMNode} node
   *         The node to be checked for events.
   * @return {Boolean}
   *         True if the node has event listeners, false otherwise.
   */

  hasEventListeners(node) {
    for (const collector of this.eventCollectors) {
      if (collector.hasListeners(node)) {
        return true;
      }
    }

    return false;
  }

  /**
   * We allow displaying chrome events if the page is chrome or if
   * `devtools.chrome.enabled = true`.
   */

  get chromeEnabled() {
    if (typeof this._chromeEnabled === "undefined") {
      this._chromeEnabled = Services.prefs.getBoolPref(
        "devtools.chrome.enabled"
      );
    }

    return this._chromeEnabled;
  }

  /**
   *
   * @param  {DOMNode} node
   *         The node for which events are to be gathered.
   * @return {Array<Object>}
   *         An array containing objects in the following format:
   *         {
   *           {String} type: The event type, e.g. "click"
   *           {Function} handler: The function called when event is triggered.
   *           {Boolean} enabled: Whether the listener is enabled or not (event listeners can
   *                     be disabled via the inspector)
   *           {String} tags: Comma separated list of tags displayed inside event bubble (e.g. "JQuery")
   *           {Object} hide: Flags for hiding certain properties.
   *             {Boolean} capturing
   *           }
   *           {Boolean} native
   *           {String|undefined} sourceActor: The sourceActor id of the event listener
   *           {nsIEventListenerInfo|undefined} nsIEventListenerInfo
   *         }
   */

  getEventListeners(node) {
    const listenerArray = [];
    let dbg;
    if (!this.chromeEnabled) {
      dbg = new Debugger();
    } else {
      // When the chrome pref is turned on, we may try to debug system compartments.
      // But since bug 1517210, the server is also loaded using the system principal
      // and so here, we have to ensure using a special Debugger instance, loaded
      // in a compartment flagged with invisibleToDebugger=true. This helps the Debugger
      // know about the precise boundary between debuggee and debugger code.
      const ChromeDebugger = require("ChromeDebugger");
      dbg = new ChromeDebugger();
    }

    for (const collector of this.eventCollectors) {
      const listeners = collector.getListeners(node);

      if (!listeners) {
        continue;
      }

      for (const listener of listeners) {
        const eventObj = this.processHandlerForEvent(
          listener,
          dbg,
          collector.normalizeListener
        );
        if (eventObj) {
          listenerArray.push(eventObj);
        }
      }
    }

    listenerArray.sort((a, b) => {
      return a.type.localeCompare(b.type);
    });

    return listenerArray;
  }

  /**
   * Process an event listener.
   *
   * @param  {EventListener} listener
   *         The event listener to process.
   * @param  {Debugger} dbg
   *         Debugger instance.
   * @param  {Function|null} normalizeListener
   *         An optional function that will be called to retrieve data about the listener.
   *         It should be a *Collector method.
   *
   * @return {Array}
   *         An array of objects where a typical object looks like this:
   *           {
   *             type: "click",
   *             handler: function() { doSomething() },
   *             origin: "http://www.mozilla.com",
   *             tags: tags,
   *             capturing: true,
   *             hide: {
   *               capturing: true
   *             },
   *             native: false,
   *             enabled: true
   *             sourceActor: "sourceActor.1234",
   *             nsIEventListenerInfo: nsIEventListenerInfo {…},
   *           }
   */

  // eslint-disable-next-line complexity
  processHandlerForEvent(listener, dbg, normalizeListener) {
    let globalDO;
    let eventObj;

    try {
      const { capturing, handler } = listener;

      const global = Cu.getGlobalForObject(handler);

      // It is important that we recreate the globalDO for each handler because
      // their global object can vary e.g. resource:// URLs on a video control. If
      // we don't do this then all chrome listeners simply display "native code."
      globalDO = dbg.addDebuggee(global);
      let listenerDO = globalDO.makeDebuggeeValue(handler);

      if (normalizeListener) {
        listenerDO = normalizeListener(listenerDO, listener);
      }

      const hide = listener.hide || {};
      const override = listener.override || {};
      const tags = listener.tags || "";
      const type = listener.type || "";
      const enabled = !!listener.enabled;
      let functionSource = handler.toString();
      let line = 0;
      let column = null;
      let native = false;
      let url = "";
      let sourceActor = "";

      // If the listener is an object with a 'handleEvent' method, use that.
      if (
        listenerDO.class === "Object" ||
        /^XUL\w*Element$/.test(listenerDO.class)
      ) {
        let desc;

        while (!desc && listenerDO) {
          desc = listenerDO.getOwnPropertyDescriptor("handleEvent");
          listenerDO = listenerDO.proto;
        }

        if (desc?.value) {
          listenerDO = desc.value;
        }
      }

      // If the listener is bound to a different context then we need to switch
      // to the bound function.
      if (listenerDO.isBoundFunction) {
        listenerDO = listenerDO.boundTargetFunction;
      }

      const { isArrowFunction, name, script, parameterNames } = listenerDO;

      if (script) {
        const scriptSource = script.source.text;

        // NOTE: Debugger.Script.prototype.startColumn is 1-based.
        //       Convert to 0-based, while keeping the wasm's column (1) as is.
        //       (bug 1863878)
        const columnBase = script.format === "wasm" ? 0 : 1;

        line = script.startLine;
        column = script.startColumn - columnBase;
        url = script.url;
        const actor = this.targetActor.sourcesManager.getOrCreateSourceActor(
          script.source
        );
        sourceActor = actor ? actor.actorID : null;

        // Checking for the string "[native code]" is the only way at this point
        // to check for native code. Even if this provides a false positive then
        // grabbing the source code a second time is harmless.
        if (
          functionSource === "[object Object]" ||
          functionSource === "[object XULElement]" ||
          functionSource.includes("[native code]")
        ) {
          functionSource = scriptSource.substr(
            script.sourceStart,
            script.sourceLength
          );

          // At this point the script looks like this:
          // () { ... }
          // We prefix this with "function" if it is not a fat arrow function.
          if (!isArrowFunction) {
            functionSource = "function " + functionSource;
          }
        }
      } else {
        // If the listener is a native one (provided by C++ code) then we have no
        // access to the script. We use the native flag to prevent showing the
        // debugger button because the script is not available.
        native = true;
      }

      // Arrow function text always contains the parameters. Function
      // parameters are often missing e.g. if Array.sort is used as a handler.
      // If they are missing we provide the parameters ourselves.
      if (parameterNames && parameterNames.length) {
        const prefix = "function " + name + "()";
        const paramString = parameterNames.join(", ");

        if (functionSource.startsWith(prefix)) {
          functionSource = functionSource.substr(prefix.length);

          functionSource = `function ${name} (${paramString})${functionSource}`;
        }
      }

      // If the listener is native code we display the filename "[native code]."
      // This is the official string and should *not* be translated.
      let origin;
      if (native) {
        origin = "[native code]";
      } else {
        origin =
          url +
          (line ? ":" + line + (column === null ? "" : ":" + column) : "");
      }

      eventObj = {
        type: override.type || type,
        handler: override.handler || functionSource.trim(),
        origin: override.origin || origin,
        tags: override.tags || tags,
        capturing:
          typeof override.capturing !== "undefined"
            ? override.capturing
            : capturing,
        hide: typeof override.hide !== "undefined" ? override.hide : hide,
        native,
        sourceActor,
        nsIEventListenerInfo: listener.nsIEventListenerInfo,
        enabled,
      };

      // Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
      // generated dynamically from e.g. an onclick="" attribute so the script
      // doesn't actually exist.
      if (!sourceActor) {
        eventObj.hide.debugger = true;
      }
    } finally {
      // Ensure that we always remove the debuggee.
      if (globalDO) {
        dbg.removeDebuggee(globalDO);
      }
    }

    return eventObj;
  }
}

exports.EventCollector = EventCollector;

Messung V0.5
C=92 H=93 G=92

¤ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge