From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- devtools/server/actors/inspector/walker.js | 2764 ++++++++++++++++++++++++++++ 1 file changed, 2764 insertions(+) create mode 100644 devtools/server/actors/inspector/walker.js (limited to 'devtools/server/actors/inspector/walker.js') diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js new file mode 100644 index 0000000000..f8da1385e9 --- /dev/null +++ b/devtools/server/actors/inspector/walker.js @@ -0,0 +1,2764 @@ +/* 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 { Actor } = require("resource://devtools/shared/protocol.js"); +const { walkerSpec } = require("resource://devtools/shared/specs/walker.js"); + +const { + LongStringActor, +} = require("resource://devtools/server/actors/string.js"); +const { + EXCLUDED_LISTENER, +} = require("resource://devtools/server/actors/inspector/constants.js"); + +loader.lazyRequireGetter( + this, + "nodeFilterConstants", + "resource://devtools/shared/dom-node-filter-constants.js" +); + +loader.lazyRequireGetter( + this, + [ + "getFrameElement", + "isAfterPseudoElement", + "isBeforePseudoElement", + "isDirectShadowHostChild", + "isMarkerPseudoElement", + "isFrameBlockedByCSP", + "isFrameWithChildTarget", + "isShadowHost", + "isShadowRoot", + "loadSheet", + ], + "resource://devtools/shared/layout/utils.js", + true +); + +loader.lazyRequireGetter( + this, + "throttle", + "resource://devtools/shared/throttle.js", + true +); + +loader.lazyRequireGetter( + this, + [ + "allAnonymousContentTreeWalkerFilter", + "findGridParentContainerForNode", + "isNodeDead", + "noAnonymousContentTreeWalkerFilter", + "nodeDocument", + "standardTreeWalkerFilter", + ], + "resource://devtools/server/actors/inspector/utils.js", + true +); + +loader.lazyRequireGetter( + this, + "CustomElementWatcher", + "resource://devtools/server/actors/inspector/custom-element-watcher.js", + true +); +loader.lazyRequireGetter( + this, + ["DocumentWalker", "SKIP_TO_SIBLING"], + "resource://devtools/server/actors/inspector/document-walker.js", + true +); +loader.lazyRequireGetter( + this, + ["NodeActor", "NodeListActor"], + "resource://devtools/server/actors/inspector/node.js", + true +); +loader.lazyRequireGetter( + this, + "NodePicker", + "resource://devtools/server/actors/inspector/node-picker.js", + true +); +loader.lazyRequireGetter( + this, + "LayoutActor", + "resource://devtools/server/actors/layout.js", + true +); +loader.lazyRequireGetter( + this, + ["getLayoutChangesObserver", "releaseLayoutChangesObserver"], + "resource://devtools/server/actors/reflow.js", + true +); +loader.lazyRequireGetter( + this, + "WalkerSearch", + "resource://devtools/server/actors/utils/walker-search.js", + true +); + +// ContentDOMReference requires ChromeUtils, which isn't available in worker context. +const lazy = {}; +if (!isWorker) { + loader.lazyGetter( + lazy, + "ContentDOMReference", + () => + ChromeUtils.importESModule( + "resource://gre/modules/ContentDOMReference.sys.mjs", + { + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + loadInDevToolsLoader: false, + } + ).ContentDOMReference + ); +} + +loader.lazyServiceGetter( + this, + "eventListenerService", + "@mozilla.org/eventlistenerservice;1", + "nsIEventListenerService" +); + +// Minimum delay between two "new-mutations" events. +const MUTATIONS_THROTTLING_DELAY = 100; +// List of mutation types that should -not- be throttled. +const IMMEDIATE_MUTATIONS = ["pseudoClassLock"]; + +const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__"; + +// The possible completions to a ':' with added score to give certain values +// some preference. +const PSEUDO_SELECTORS = [ + [":active", 1], + [":hover", 1], + [":focus", 1], + [":visited", 0], + [":link", 0], + [":first-letter", 0], + [":first-child", 2], + [":before", 2], + [":after", 2], + [":lang(", 0], + [":not(", 3], + [":first-of-type", 0], + [":last-of-type", 0], + [":only-of-type", 0], + [":only-child", 2], + [":nth-child(", 3], + [":nth-last-child(", 0], + [":nth-of-type(", 0], + [":nth-last-of-type(", 0], + [":last-child", 2], + [":root", 0], + [":empty", 0], + [":target", 0], + [":enabled", 0], + [":disabled", 0], + [":checked", 1], + ["::selection", 0], + ["::marker", 0], +]; + +const HELPER_SHEET = + "data:text/css;charset=utf-8," + + encodeURIComponent(` + .__fx-devtools-hide-shortcut__ { + visibility: hidden !important; + } +`); + +/** + * We only send nodeValue up to a certain size by default. This stuff + * controls that size. + */ +exports.DEFAULT_VALUE_SUMMARY_LENGTH = 50; +var gValueSummaryLength = exports.DEFAULT_VALUE_SUMMARY_LENGTH; + +exports.getValueSummaryLength = function () { + return gValueSummaryLength; +}; + +exports.setValueSummaryLength = function (val) { + gValueSummaryLength = val; +}; + +/** + * Server side of the DOM walker. + */ +class WalkerActor extends Actor { + /** + * Create the WalkerActor + * @param {DevToolsServerConnection} conn + * The server connection. + * @param {TargetActor} targetActor + * The top-level Actor for this tab. + * @param {Object} options + * - {Boolean} showAllAnonymousContent: Show all native anonymous content + */ + constructor(conn, targetActor, options) { + super(conn, walkerSpec); + this.targetActor = targetActor; + this.rootWin = targetActor.window; + this.rootDoc = this.rootWin.document; + + // Map of already created node actors, keyed by their corresponding DOMNode. + this._nodeActorsMap = new Map(); + + this._pendingMutations = []; + this._activePseudoClassLocks = new Set(); + this._mutationBreakpoints = new WeakMap(); + this._anonParents = new WeakMap(); + this.customElementWatcher = new CustomElementWatcher( + targetActor.chromeEventHandler + ); + + // In this map, the key-value pairs are the overflow causing elements and their + // respective ancestor scrollable node actor. + this.overflowCausingElementsMap = new Map(); + + this.showAllAnonymousContent = options.showAllAnonymousContent; + + this.walkerSearch = new WalkerSearch(this); + + // Nodes which have been removed from the client's known + // ownership tree are considered "orphaned", and stored in + // this set. + this._orphaned = new Set(); + + // The client can tell the walker that it is interested in a node + // even when it is orphaned with the `retainNode` method. This + // list contains orphaned nodes that were so retained. + this._retainedOrphans = new Set(); + + this.onSubtreeModified = this.onSubtreeModified.bind(this); + this.onSubtreeModified[EXCLUDED_LISTENER] = true; + this.onNodeRemoved = this.onNodeRemoved.bind(this); + this.onNodeRemoved[EXCLUDED_LISTENER] = true; + this.onAttributeModified = this.onAttributeModified.bind(this); + this.onAttributeModified[EXCLUDED_LISTENER] = true; + + this.onMutations = this.onMutations.bind(this); + this.onSlotchange = this.onSlotchange.bind(this); + this.onShadowrootattached = this.onShadowrootattached.bind(this); + this.onAnonymousrootcreated = this.onAnonymousrootcreated.bind(this); + this.onAnonymousrootremoved = this.onAnonymousrootremoved.bind(this); + this.onFrameLoad = this.onFrameLoad.bind(this); + this.onFrameUnload = this.onFrameUnload.bind(this); + this.onCustomElementDefined = this.onCustomElementDefined.bind(this); + this._throttledEmitNewMutations = throttle( + this._emitNewMutations.bind(this), + MUTATIONS_THROTTLING_DELAY + ); + + targetActor.on("will-navigate", this.onFrameUnload); + targetActor.on("window-ready", this.onFrameLoad); + + this.customElementWatcher.on( + "element-defined", + this.onCustomElementDefined + ); + + // Keep a reference to the chromeEventHandler for the current targetActor, to make + // sure we will be able to remove the listener during the WalkerActor destroy(). + this.chromeEventHandler = targetActor.chromeEventHandler; + // shadowrootattached is a chrome-only event. We enable it below. + this.chromeEventHandler.addEventListener( + "shadowrootattached", + this.onShadowrootattached + ); + // anonymousrootcreated is a chrome-only event. We enable it below. + this.chromeEventHandler.addEventListener( + "anonymousrootcreated", + this.onAnonymousrootcreated + ); + this.chromeEventHandler.addEventListener( + "anonymousrootremoved", + this.onAnonymousrootremoved + ); + for (const { document } of this.targetActor.windows) { + document.devToolsAnonymousAndShadowEventsEnabled = true; + } + + // Ensure that the root document node actor is ready and + // managed. + this.rootNode = this.document(); + + this.layoutChangeObserver = getLayoutChangesObserver(this.targetActor); + this._onReflows = this._onReflows.bind(this); + this.layoutChangeObserver.on("reflows", this._onReflows); + this._onResize = this._onResize.bind(this); + this.layoutChangeObserver.on("resize", this._onResize); + + this._onEventListenerChange = this._onEventListenerChange.bind(this); + eventListenerService.addListenerChangeListener(this._onEventListenerChange); + } + + get nodePicker() { + if (!this._nodePicker) { + this._nodePicker = new NodePicker(this, this.targetActor); + } + + return this._nodePicker; + } + + watchRootNode() { + if (this.rootNode) { + this.emit("root-available", this.rootNode); + } + } + + /** + * Callback for eventListenerService.addListenerChangeListener + * @param nsISimpleEnumerator changesEnum + * enumerator of nsIEventListenerChange + */ + _onEventListenerChange(changesEnum) { + for (const current of changesEnum.enumerate(Ci.nsIEventListenerChange)) { + const target = current.target; + + if (this._nodeActorsMap.has(target)) { + const actor = this.getNode(target); + const mutation = { + type: "events", + target: actor.actorID, + hasEventListeners: actor._hasEventListeners, + }; + this.queueMutation(mutation); + } + } + } + + // Returns the JSON representation of this object over the wire. + form() { + return { + actor: this.actorID, + root: this.rootNode.form(), + traits: {}, + }; + } + + toString() { + return "[WalkerActor " + this.actorID + "]"; + } + + getDocumentWalker(node, skipTo) { + // Allow native anon content (like