/* 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 { Cu } = require("chrome"); const Services = require("Services"); const InspectorUtils = require("InspectorUtils"); const protocol = require("devtools/shared/protocol"); const { PSEUDO_CLASSES } = require("devtools/shared/css/constants"); const { nodeSpec, nodeListSpec } = require("devtools/shared/specs/node"); loader.lazyRequireGetter( this, ["getCssPath", "getXPath", "findCssSelector", "findAllCssSelectors"], "devtools/shared/inspector/css-logic", true ); loader.lazyRequireGetter( this, [ "isAfterPseudoElement", "isAnonymous", "isBeforePseudoElement", "isDirectShadowHostChild", "isMarkerPseudoElement", "isNativeAnonymous", "isShadowHost", "isShadowRoot", "getShadowRootMode", "isRemoteFrame", ], "devtools/shared/layout/utils", true ); loader.lazyRequireGetter( this, [ "getBackgroundColor", "getClosestBackgroundColor", "getNodeDisplayName", "imageToImageData", "isNodeDead", ], "devtools/server/actors/inspector/utils", true ); loader.lazyRequireGetter( this, "LongStringActor", "devtools/server/actors/string", true ); loader.lazyRequireGetter( this, "getFontPreviewData", "devtools/server/actors/utils/style-utils", true ); loader.lazyRequireGetter( this, "CssLogic", "devtools/server/actors/inspector/css-logic", true ); loader.lazyRequireGetter( this, "EventCollector", "devtools/server/actors/inspector/event-collector", true ); loader.lazyRequireGetter( this, "DOMHelpers", "devtools/shared/dom-helpers", true ); const SUBGRID_ENABLED = Services.prefs.getBoolPref( "layout.css.grid-template-subgrid-value.enabled" ); const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog"; const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20; /** * Server side of the node actor. */ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { initialize: function(walker, node) { protocol.Actor.prototype.initialize.call(this, null); this.walker = walker; this.rawNode = node; this._eventCollector = new EventCollector(this.walker.targetActor); // Store the original display type and scrollable state and whether or not the node is // displayed to track changes when reflows occur. const wasScrollable = this.isScrollable; this.currentDisplayType = this.displayType; this.wasDisplayed = this.isDisplayed; this.wasScrollable = wasScrollable; if (wasScrollable) { this.walker.updateOverflowCausingElements( this, this.walker.overflowCausingElementsMap ); } }, toString: function() { return ( "[NodeActor " + this.actorID + " for " + this.rawNode.toString() + "]" ); }, /** * Instead of storing a connection object, the NodeActor gets its connection * from its associated walker. */ get conn() { return this.walker.conn; }, isDocumentElement: function() { return ( this.rawNode.ownerDocument && this.rawNode.ownerDocument.documentElement === this.rawNode ); }, destroy: function() { protocol.Actor.prototype.destroy.call(this); if (this.mutationObserver) { if (!Cu.isDeadWrapper(this.mutationObserver)) { this.mutationObserver.disconnect(); } this.mutationObserver = null; } if (this.slotchangeListener) { if (!isNodeDead(this)) { this.rawNode.removeEventListener("slotchange", this.slotchangeListener); } this.slotchangeListener = null; } this._eventCollector.destroy(); this._eventCollector = null; this.rawNode = null; this.walker = null; }, // Returns the JSON representation of this object over the wire. form: function() { const parentNode = this.walker.parentNode(this); const inlineTextChild = this.walker.inlineTextChild(this); const shadowRoot = isShadowRoot(this.rawNode); const hostActor = shadowRoot ? this.walker.getNode(this.rawNode.host) : null; const form = { actor: this.actorID, host: hostActor ? hostActor.actorID : undefined, baseURI: this.rawNode.baseURI, parent: parentNode ? parentNode.actorID : undefined, nodeType: this.rawNode.nodeType, namespaceURI: this.rawNode.namespaceURI, nodeName: this.rawNode.nodeName, nodeValue: this.rawNode.nodeValue, displayName: getNodeDisplayName(this.rawNode), numChildren: this.numChildren, inlineTextChild: inlineTextChild ? inlineTextChild.form() : undefined, displayType: this.displayType, isScrollable: this.isScrollable, isTopLevelDocument: this.isTopLevelDocument, causesOverflow: this.walker.overflowCausingElementsMap.has(this.rawNode), // doctype attributes name: this.rawNode.name, publicId: this.rawNode.publicId, systemId: this.rawNode.systemId, attrs: this.writeAttrs(), customElementLocation: this.getCustomElementLocation(), isMarkerPseudoElement: isMarkerPseudoElement(this.rawNode), isBeforePseudoElement: isBeforePseudoElement(this.rawNode), isAfterPseudoElement: isAfterPseudoElement(this.rawNode), isAnonymous: isAnonymous(this.rawNode), isNativeAnonymous: isNativeAnonymous(this.rawNode), isShadowRoot: shadowRoot, shadowRootMode: getShadowRootMode(this.rawNode), isShadowHost: isShadowHost(this.rawNode), isDirectShadowHostChild: isDirectShadowHostChild(this.rawNode), pseudoClassLocks: this.writePseudoClassLocks(), mutationBreakpoints: this.walker.getMutationBreakpoints(this), isDisplayed: this.isDisplayed, isInHTMLDocument: this.rawNode.ownerDocument && this.rawNode.ownerDocument.contentType === "text/html", hasEventListeners: this._hasEventListeners, traits: {}, }; if (this.isDocumentElement()) { form.isDocumentElement = true; } // Flag the remote frame and declare at least one child (the #document element) so // that they can be expanded. if (this.isRemoteFrame) { form.remoteFrame = true; form.numChildren = 1; form.browsingContextID = this.rawNode.browsingContext.id; } return form; }, /** * Watch the given document node for mutations using the DOM observer * API. */ watchDocument: function(doc, callback) { const node = this.rawNode; // Create the observer on the node's actor. The node will make sure // the observer is cleaned up when the actor is released. const observer = new doc.defaultView.MutationObserver(callback); observer.mergeAttributeRecords = true; observer.observe(node, { nativeAnonymousChildList: true, attributes: true, characterData: true, characterDataOldValue: true, childList: true, subtree: true, }); this.mutationObserver = observer; }, /** * Watch for all "slotchange" events on the node. */ watchSlotchange: function(callback) { this.slotchangeListener = callback; this.rawNode.addEventListener("slotchange", this.slotchangeListener); }, /** * Check if the current node is representing a remote frame. * In the context of the browser toolbox, a remote frame can be the * element found inside each tab. * In the context of the content toolbox, a remote frame can be a