/* 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 { Cu } = require("chrome"); const Services = require("Services"); const { isAfterPseudoElement, isBeforePseudoElement, isMarkerPseudoElement, isNativeAnonymous, } = require("devtools/shared/layout/utils"); const Debugger = require("Debugger"); const { EXCLUDED_LISTENER, } = require("devtools/server/actors/inspector/constants"); // 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 }) { 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) { let listeners; if ( typeof node.nodeName !== "undefined" && node.nodeName.toLowerCase() === "html" ) { const winListeners = Services.els.getListenerInfoFor(node.ownerGlobal) || []; const docElementListeners = Services.els.getListenerInfoFor(node) || []; const docListeners = Services.els.getListenerInfoFor(node.parentNode) || []; listeners = [...winListeners, ...docElementListeners, ...docListeners]; } else { listeners = Services.els.getListenerInfoFor(node) || []; } return listeners.filter(listener => { const obj = this.unwrap(listener.listenerObject); return !obj || !obj[EXCLUDED_LISTENER]; }); } 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