diff --git a/devtools/client/inspector/markup/markup.js b/devtools/client/inspector/markup/markup.js index cd23838419cb..f569f4afc6c5 100644 --- a/devtools/client/inspector/markup/markup.js +++ b/devtools/client/inspector/markup/markup.js @@ -2191,13 +2191,18 @@ MarkupView.prototype = { /** * Return a list of the children to display for this container. */ - _getVisibleChildren: function(container, centered) { + _getVisibleChildren: async function(container, centered) { let maxChildren = container.maxChildren || this.maxChildren; if (maxChildren == -1) { maxChildren = undefined; } - return this.walker.children(container.node, { + // We have to use node's walker and not a top level walker + // as for fission frames, we are going to have multiple walkers + const inspectorFront = await container.node.targetFront.getFront( + "inspector" + ); + return inspectorFront.walker.children(container.node, { maxNodes: maxChildren, center: centered, }); diff --git a/devtools/server/actors/inspector/node.js b/devtools/server/actors/inspector/node.js index 5af7c2b70d44..e24cd391fa9d 100644 --- a/devtools/server/actors/inspector/node.js +++ b/devtools/server/actors/inspector/node.js @@ -6,10 +6,12 @@ const { Cu } = require("chrome"); const Services = require("Services"); +const ChromeUtils = require("ChromeUtils"); 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"); +const { DebuggerServer } = require("devtools/server/debugger-server"); loader.lazyRequireGetter( this, @@ -143,6 +145,11 @@ const SUBGRID_ENABLED = Services.prefs.getBoolPref( "layout.css.grid-template-subgrid-value.enabled" ); +const BROWSER_TOOLBOX_FISSION_ENABLED = Services.prefs.getBoolPref( + "devtools.browsertoolbox.fission", + false +); + const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog"; const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20; @@ -263,6 +270,13 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { 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; + } + return form; }, @@ -295,6 +309,19 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { this.rawNode.addEventListener("slotchange", this.slotchangeListener); }, + /** + * Check if the current node is representing a remote frame. + * EXPERIMENTAL: Only works if fission is enabled in the toolbox. + */ + get isRemoteFrame() { + return ( + this.numChildren == 0 && + ChromeUtils.getClassName(this.rawNode) == "XULFrameElement" && + this.rawNode.getAttribute("remote") == "true" && + BROWSER_TOOLBOX_FISSION_ENABLED + ); + }, + // Estimate the number of children that the walker will return without making // a call to children() if possible. get numChildren() { @@ -671,6 +698,21 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { innerHeight: win.innerHeight, }; }, + + /** + * Fetch the target actor's form for the current remote frame. + * + * (to be called only if form.remoteFrame is true) + */ + connectToRemoteFrame() { + if (!this.isRemoteFrame) { + return { + error: "ErrorRemoteFrame", + message: "Tried to call `connectToRemoteFrame` on a local frame", + }; + } + return DebuggerServer.connectToFrame(this.conn, this.rawNode); + }, }); /** diff --git a/devtools/shared/fronts/inspector.js b/devtools/shared/fronts/inspector.js index 80d1d79110c5..ff909f0755f1 100644 --- a/devtools/shared/fronts/inspector.js +++ b/devtools/shared/fronts/inspector.js @@ -452,6 +452,25 @@ class WalkerFront extends FrontClassWithSpec(walkerSpec) { nextSibling: nextSibling, }; } + + async children(node, options) { + if (!node.remoteFrame) { + return super.children(node, options); + } + // First get the target actor form of this remote frame element + const target = await node.connectToRemoteFrame(); + // Then get an inspector front, and grab its walker front + const walker = (await target.getFront("inspector")).walker; + // Finally retrieve the NodeFront of the remote frame's document + const documentNode = await walker.getRootNode(); + + // And return the same kind of response `walker.children` returns + return { + nodes: [documentNode], + hasFirst: true, + hasLast: true, + }; + } } exports.WalkerFront = WalkerFront; diff --git a/devtools/shared/fronts/node.js b/devtools/shared/fronts/node.js index 87312dd3cd45..b6e5b118c770 100644 --- a/devtools/shared/fronts/node.js +++ b/devtools/shared/fronts/node.js @@ -20,6 +20,13 @@ loader.lazyRequireGetter( "devtools/shared/dom-node-constants" ); +loader.lazyRequireGetter( + this, + "BrowsingContextTargetFront", + "devtools/shared/fronts/targets/browsing-context", + true +); + const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__"; /** @@ -273,6 +280,9 @@ class NodeFront extends FrontClassWithSpec(nodeSpec) { get numChildren() { return this._form.numChildren; } + get remoteFrame() { + return this._form.remoteFrame; + } get hasEventListeners() { return this._form.hasEventListeners; } @@ -497,6 +507,20 @@ class NodeFront extends FrontClassWithSpec(nodeSpec) { } return actor.rawNode; } + + async connectToRemoteFrame() { + if (this._remoteFrameTarget) { + return this._remoteFrameTarget; + } + // First get the target actor form of this remote frame element + const form = await super.connectToRemoteFrame(); + // Build the related Target object + this._remoteFrameTarget = new BrowsingContextTargetFront(this.conn); + this._remoteFrameTarget.actorID = form.actor; + this._remoteFrameTarget.form(form); + this._remoteFrameTarget.manage(this._remoteFrameTarget); + return this._remoteFrameTarget; + } } exports.NodeFront = NodeFront; diff --git a/devtools/shared/specs/node.js b/devtools/shared/specs/node.js index 691aef253994..12f5c1c7f8eb 100644 --- a/devtools/shared/specs/node.js +++ b/devtools/shared/specs/node.js @@ -134,6 +134,16 @@ const nodeSpec = generateActorSpec({ request: {}, response: RetVal("windowDimensions"), }, + connectToRemoteFrame: { + request: {}, + // We are passing a target actor form here. + // As we are manually fetching the form JSON via DebuggerServer.connectToFrame, + // we are not instanciating a protocol.js front class and can't use proper type + // here and have automatic marshalling. + // + // Alex: Can we do something to address that?? + response: RetVal("json"), + }, }, });