Skip to content

Latest commit

 

History

History
325 lines (247 loc) · 30.6 KB

DevTools.md

File metadata and controls

325 lines (247 loc) · 30.6 KB

Firefox DevTools Integration

Ladybird contains an experimental, work-in-progress DevTools server. This document describes how to use the server and the protocol used to communicate with the DevTools client.

Using DevTools

The DevTools server may be enabled with the --devtools command line flag when launching Ladybird. This flag expects a port for the DevTools server to listen on. For example:

./Meta/ladybird.sh run ladybird --devtools 6000 https://ladybird.org/

Once the browser is running, in Firefox, navigate to about:debugging and select the "Setup" tab. In the "Network Location" form, enter the DevTools server address. In the above example, this will be localhost:6000. You will only have to enter this information once:

DevTools setup

This will make the address appear on the left-side of the page. Click "Connect":

DevTools connect

Once connected, the listed address will now be clickable itself. Click it to be brought to a page which shows Ladybird's tab list:

DevTools tab list

Click the "Inspect" button next to the tab you wish to debug. This will open another tab in Firefox with an inspector panel, which you may use to view the DOM tree:

DevTools DOM tree

DevTools protocol

Firefox's documentation of its DevTools protocol is quite incomplete, and even incorrect in some places. To aid with development, this section describes the protocol that Ladybird has implemented. Note that this is only observed behavior - our implementation could certainly be incorrect or incomplete in places.

The protocol itself is based on "actors". Each packet contains a JSON object. Messages sent from the DevTools client contain a "to" field indicating the actor for which the message is intended. Messages sent from the DevTools server contain a "from" field indicating the actor from which the message originated.

A very important aspect of this protocol is that the client may send multiple requests to an actor at once without waiting for a reply, to which the actor must then reply in order. Any requests that must be performed asynchronously (such as fetching the serialized DOM tree from the WebContent process) must block the actor from sending replies for subsequent requests.

To log communcation between the DevTools server and client, enable the DEVTOOLS_DEBUG flag:

cmake -B Build/release -D DEVTOOLS_DEBUG=ON

Logged messages beginning with "<<" were sent from the server to the client. Messages beginning with ">>" were sent from the client to the server.

Session initialization

Once the DevTools client has connected, communication begins with a root actor. The root actor initiates the session by sending a message to the client describing itself:

<< {"from":"root","applicationType":"browser","traits":{"sources":false,"highlightable":true,"customHighlighters":true,"networkMonitor":false}}

The client then sends a connection request, to which the server replies with an empty message:

>> {"type":"connect","frontendVersion":"135.0","to":"root"}
<< {"from":"root"}

The client then asks the root actor to describe other top-level actors. We are required to provide a device actor and a preference actor:

>> {"type":"getRoot","to":"root"}
<< {"from":"root","selected":0,"deviceActor":"server0-device1","preferenceActor":"server0-preference2"}

The device actor provides information about the device for which the server is running, such as application version and host OS. We provide a subset of fields that Firefox itself provides, as the other fields seem optional, and may not even make sense for Ladybird.

The client will immediately request this information after the getRoot reply:

>> {"type":"getDescription","to":"server0-device1"}
<< {"from":"server0-device1","value":{"apptype":"ladybird","name":"Ladybird","brandName":"Ladybird","version":"1.0","appbuildid":"Version 1.0","platformbuildid":"Version 1.0","platformversion":"135.0","useragent":"Mozilla/5.0 (macOS; AArch64) Ladybird/1.0","os":"macOS","arch":"AArch64"}}

The preference actor is used to query and update configuration options that resemble those found in Firefox's about:config page. We don't implement anything concrete here. The client will request a few boolean configuration options after the device request, to which the server will just reply with false:

>> {"type":"getBoolPref","value":"devtools.debugger.prompt-connection","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"browser.privatebrowsing.autostart","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"dom.serviceWorkers.enabled","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}

The client then asks for a list of add-ons, workers, and service workers, to which the server replies with an empty list:

>> {"type":"listTabs","to":"root"}
<< {"from":"root","addons":[]}

>> {"type":"listWorkers","to":"root"}
<< {"from":"root","workers":[]}

>> {"type":"listServiceWorkerRegistrations","to":"root"}
<< {"from":"root","registrations":[]}

Then client then asks for a list of processes, followed immediately by a request for the zeroth process. This zeroth process is required, and seems to correspond to the browser's main process. We currently stub out this information:

>> {"type":"listProcesses","to":"root"}
>> {"type":"getProcess","id":0,"to":"root"}

<< {"from":"root","processes":[{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}
<< {"from":"root","processDescriptor":{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}}

The client then asks for a list of tabs. The server contains a delegate interface to be implemented by the WebView application. This interface includes a method to form a list open tabs. The server will reply with this list:

>> {"type":"listTabs","to":"root"}
<< {"from":"root","tabs":[{"actor":"server0-tab4","title":"Ladybird","url":"https://ladybird.org/","browserId":1,"browsingContextID":1,"outerWindowID":1,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}

The client then asks for information about the tabs, including their favicon. The favicon is expected to be a URL, but when provided, the DevTools client seems to hang. So we use a null value here, which Firefox itself also uses. The server will reply with the same information provided in the listTabs request:

>> {"type":"getFavicon","to":"server0-tab4"}
<< {"from":"server0-tab4","favicon":null}

>> {"type":"getTab","browserId":1,"to":"root"}
<< {"from":"root","tab":{"actor":"server0-tab4","title":"Ladybird","url":"https://ladybird.org/","browserId":1,"browsingContextID":1,"outerWindowID":1,"traits":{"watcher":true,"supportsReloadDescriptor":true}}}

At this point, the session is established and Ladybird is listed on about:debugging.

Inspecting a DOM tree

When the user inspects a tab, the client initiates the inspection with a request for a "watcher". Watchers are the actors responsible for monitoring a wide variety of components. We currently only implement a "frame" watcher. The server will create a watcher associated with the inspected tab, and reply with a set of watcher options indicating our support of frame inspection:

>> {"type":"getWatcher","isServerTargetSwitchingEnabled":true,"isPopupDebuggingEnabled":false,"to":"server0-tab4"}
<< {"from":"server0-tab4","actor":"server0-watcher5","traits":{"shared_worker":false,"service_worker":false,"frame":true,"process":false,"worker":false,"resources":{"Cache":false,"console-message":false,"cookies":false,"css-change":false,"css-message":false,"css-registered-properties":false,"document-event":false,"error-message":false,"extension-storage":false,"indexed-db":false,"jstracer-state":false,"jstracer-trace":false,"last-private-context-exit":false,"local-storage":false,"network-event":false,"network-event-stacktrace":false,"platform-message":false,"reflow":false,"server-sent-event":false,"session-storage":false,"source":false,"stylesheet":false,"thread-state":false,"websocket":false}}}

The client then asks the server to watch the inspected tab's frame. The server must reply with multiple messages here. The first message contains information about the inspected tab, as well as a list of other actors associated with the watcher. We are required to have an inspector actor, a CSS properties actor, and a thread actor (described below when they are requested by the client). The second message contains a small set of information about the tab again. The third message is just an empty message, which seems to indicate and end-of-transmission status:

>> {"type":"watchTargets","targetType":"frame","to":"server0-watcher5"}
<< {"from":"server0-watcher5","type":"target-available-form","target":{"actor":"server0-frame9","title":"Ladybird","url":"https://ladybird.org/","browsingContextID":1,"outerWindowID":1,"isTopLevelTarget":true,"traits":{"frames":true,"isBrowsingContext":true,"logInPage":false,"navigation":true,"supportsTopLevelTargetFlag":true,"watchpoints":true},"cssPropertiesActor":"server0-css-properties6","inspectorActor":"server0-inspector7","threadActor":"server0-thread8"}}
<< {"from":"server0-frame9","type":"frameUpdate","frames":[{"id":1,"title":"Ladybird","url":"https://ladybird.org/"}]}
<< {"from":"server0-watcher5"}

The client then asks for a couple of configuration actors, and sends some configuration options to those actors. These actors are a target configuration actor and a thread configuration actor. The target configuration actor informs the client about features supported by the target tab, such as an "offline mode". We currently stub these options to indicate all features are not supported. The thread configuration actor is currently entirely a stub.

>> {"type":"getTargetConfigurationActor","to":"server0-watcher5"}
<< {"from":"server0-watcher5","configuration":{"actor":"server0-target-configuration10","configuration":{},"traits":{"supportedOptions":{"cacheDisabled":false,"colorSchemeSimulation":false,"customFormatters":false,"customUserAgent":false,"javascriptEnabled":false,"overrideDPPX":false,"printSimulationEnabled":false,"rdmPaneMaxTouchPoints":false,"rdmPaneOrientation":false,"recordAllocations":false,"reloadOnTouchSimulationToggle":false,"restoreFocus":false,"serviceWorkersTestingEnabled":false,"setTabOffline":false,"touchEventsOverride":false,"tracerOptions":false,"useSimpleHighlightersForReducedMotion":false}}}}

>> {"type":"updateConfiguration","configuration":{"cacheDisabled":true,"customFormatters":false,"serviceWorkersTestingEnabled":false,"useSimpleHighlightersForReducedMotion":false,"isTracerFeatureEnabled":false},"to":"server0-target-configuration10"}
<< {"from":"server0-target-configuration10"}

>> {"type":"getThreadConfigurationActor","to":"server0-watcher5"}
<< {"from":"server0-watcher5","configuration":{"actor":"server0-thread-configuration11"}}

>> {"type":"updateConfiguration","configuration":{"shouldPauseOnDebuggerStatement":true,"pauseOnExceptions":false,"ignoreCaughtExceptions":true,"shouldIncludeSavedFrames":true,"shouldIncludeAsyncLiveFrames":false,"skipBreakpoints":false,"logEventBreakpoints":false,"observeAsmJS":true,"pauseOverlay":true},"to":"server0-thread-configuration11"}
<< {"from":"server0-thread-configuration11"}

The client then asks for a list of frames, to which the server replies with an empty message:

>> {"type":"listFrames","to":"server0-frame9"}
<< {"from":"server0-frame9"}

The client then asks for a CSS database. The server must reply with a list of every CSS property that the rendering engine supports. The delegate interface includes a method for the application to provide this information. LibWebView will reply with the list of properties generated by Properties.json.

(For brevity, only the font property is included here, as the list of all properties is very large.)

>> {"type":"getCSSDatabase","to":"server0-css-properties6"}
<< {"from":"server0-css-properties6","properties":{"font":{"isInherited":true,"supports":[],"values":[],"subproperties":["font"]}}}

The client then asks the inspector actor for a few more actors in a row. The inspector actor is essentially just a container that serves to hold other actors that actually perform inspection.

First, the client asks for a walker actor. This walker is what will actually own and traverse the DOM tree. It is at this point that the server fetches the DOM tree from the WebContent process. The delegate interface includes a method for the application to asynchronously provide this information. The inspector actor's message queue is blocked until we receive the DOM tree (or encounter an error). The server will reply with information about the document element.

Next, the client asks for a page style actor and a highlighter actor. The page style actor is currently stubbed to reply with some fields expected by the client. The highlighter actor is also currently stubbed, but this actor seems like what will be used to paint an overlay onto the inspected node.

>> {"type":"getWalker","options":{"showAllAnonymousContent":false},"to":"server0-inspector7"}
>> {"type":"getPageStyle","to":"server0-inspector7"}
>> {"type":"getHighlighterByType","typeName":"ViewportSizeOnResizeHighlighter","to":"server0-inspector7"}

<< {"from":"server0-inspector7","walker":{"actor":"server0-walker14","root":{"actor":"server0-walker14-node0","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"#document","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":false,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":true,"nodeName":"#document","nodeType":9,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{}}}}
<< {"from":"server0-inspector7","pageStyle":{"actor":"server0-page-style12","traits":{"fontStyleLevel4":true,"fontWeightLevel4":true,"fontStretchLevel4":true,"fontVariations":true}}}
<< {"from":"server0-inspector7","highlighter":{"actor":"server0-highlighter13"}}

The client then asks for the frame's parent browsing context, to which the server currently replies with the same context as the frame itself:

>> {"type":"getParentBrowsingContextID","browsingContextID":1,"to":"server0-watcher5"}
<< {"from":"server0-watcher5","browsingContextID":1}

The client then instructs the highlighter actor to "show" the inspector actor, to which the server currently replies with an ack:

>> {"type":"show","node":"server0-inspector7","to":"server0-highlighter13"}
<< {"from":"server0-highlighter13","value":true}

Then client then starts to ask for DOM node information. It begins by issuing a query selector request for the <body> element, followed by requests for the children of the <html> element, the document element, and the <body> element. The query selector indicates the actor name of the node from which to start searching, and the name of the requested node. We do not currently create concrete actor objects for each node; rather, we just assign actor names to each node in the DOM tree received from the WebContent process. The query selector reply includes a field to indicate that its parent is not the parent from which the search started. Each serialized DOM node contains information such as its type, display name, tag name, attributes, etc.

>> {"type":"querySelector","node":"server0-walker14-node0","selector":"body","to":"server0-walker14"}
<< {"from":"server0-walker14","node":{"actor":"server0-walker14-node17","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"body","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"BODY","nodeType":1,"nodeValue":null,"numChildren":10,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node1"},"newParents":[{"actor":"server0-walker14-node1","attrs":[{"name":"lang","value":"en"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"html","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"HTML","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node0"}]}

>> {"type":"children","node":"server0-walker14-node1","maxNodes":100,"center":"server0-walker14-node17","to":"server0-walker14"}
>> {"type":"children","node":"server0-walker14-node0","maxNodes":100,"center":"server0-walker14-node1","to":"server0-walker14"}
>> {"type":"children","node":"server0-walker14-node17","maxNodes":100,"to":"server0-walker14"}

<< {"from":"server0-walker14","hasFirst":true,"hasLast":true,"nodes":[{"actor":"server0-walker14-node2","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"head","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":false,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"HEAD","nodeType":1,"nodeValue":null,"numChildren":13,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node1"},{"actor":"server0-walker14-node17","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"body","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"BODY","nodeType":1,"nodeValue":null,"numChildren":10,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node1"}]}
<< {"from":"server0-walker14","hasFirst":true,"hasLast":true,"nodes":[{"actor":"server0-walker14-node1","attrs":[{"name":"lang","value":"en"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"html","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"HTML","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node0"}]}
<< {"from":"server0-walker14","hasFirst":true,"hasLast":true,"nodes":[{"actor":"server0-walker14-node18","attrs":[{"name":"class","value":"p-[20px] bg-[#000] relative"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"header","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"HEADER","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node38","attrs":[{"name":"class","value":"bg-[#000] relative flex flex-col items-center pt-[80px] px-5 pb-0 lg:flex-row lg:pt-0 lg:min-h-[892px]"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node55","attrs":[{"name":"id","value":"about"},{"name":"class","value":"text-[#fff] bg-[#000] pt-12 lg:pt-0 px-4"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node71","attrs":[{"name":"class","value":"why"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node112","attrs":[{"name":"class","value":"news"},{"name":"id","value":"news"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node174","attrs":[{"name":"id","value":"gi"},{"name":"class","value":"gi"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":2,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node197","attrs":[{"name":"class","value":"relative mb-24 px-5 lg:mb-28"},{"name":"id","value":"sponsors"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":3,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node322","attrs":[{"name":"class","value":"relative z-10 -top-4 text-[#fff] bg-[url('/assets/img/blurp.webp')] bg-center bg-cover mb-12 flex justify-center"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node341","attrs":[{"name":"id","value":"faq"},{"name":"class","value":"faq"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"section","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"SECTION","nodeType":1,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"},{"actor":"server0-walker14-node495","attrs":[{"name":"class","value":"bg-[#000] py-0 md:px-20"}],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"footer","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":true,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":false,"nodeName":"FOOTER","nodeType":1,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{},"parent":"server0-walker14-node17"}]}

The client then instructs the server to watch the root node. The server replies with information about the document element again, followed by an empty message. We do not currently do anything more with this request.

>> {"type":"watchRootNode","to":"server0-walker14"}
<< {"from":"server0-walker14","type":"root-available","node":{"actor":"server0-walker14-node0","attrs":[],"baseURI":"https://ladybird.org/","causesOverflow":false,"containerType":null,"displayName":"#document","displayType":"block","host":null,"isAfterPseudoElement":false,"isAnonymous":false,"isBeforePseudoElement":false,"isDirectShadowHostChild":null,"isDisplayed":false,"isInHTMLDocument":true,"isMarkerPseudoElement":false,"isNativeAnonymous":false,"isScrollable":false,"isShadowHost":false,"isShadowRoot":false,"isTopLevelDocument":true,"nodeName":"#document","nodeType":9,"nodeValue":null,"numChildren":1,"shadowRootMode":null,"traits":{}}}
<< {"from":"server0-walker14"}

At this point, the DOM tree in the DevTools client is interactable. As the user interacts with the client, the client will send similar requests as the above to retrieve more DOM node information. The server will log an error for features we do not yet support.

Known issues

  1. We occasionally fail to inspect a tab. There isn't any information logged by client or server, other than the client indicating the session was disconnected. The following is the entire communication log from an instance of this error:
<< {"from":"root","applicationType":"browser","traits":{"sources":false,"highlightable":true,"customHighlighters":true,"networkMonitor":false}}
>> {"type":"connect","frontendVersion":"135.0","to":"root"}
<< {"from":"root"}
>> {"type":"getRoot","to":"root"}
<< {"from":"root","selected":0,"preferenceActor":"server0-preference2","deviceActor":"server0-device1"}
>> {"type":"getDescription","to":"server0-device1"}
<< {"from":"server0-device1","value":{"apptype":"ladybird","name":"Ladybird","brandName":"Ladybird","version":"1.0","appbuildid":"Version 1.0","platformbuildid":"Version 1.0","platformversion":"135.0","useragent":"Mozilla/5.0 (Linux; x86_64) Ladybird/1.0","os":"Linux","arch":"x86_64"}}
>> {"type":"getDescription","to":"server0-device1"}
<< {"from":"server0-device1","value":{"apptype":"ladybird","name":"Ladybird","brandName":"Ladybird","version":"1.0","appbuildid":"Version 1.0","platformbuildid":"Version 1.0","platformversion":"135.0","useragent":"Mozilla/5.0 (Linux; x86_64) Ladybird/1.0","os":"Linux","arch":"x86_64"}}
>> {"type":"getBoolPref","value":"devtools.debugger.prompt-connection","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"browser.privatebrowsing.autostart","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"getBoolPref","value":"dom.serviceWorkers.enabled","to":"server0-preference2"}
<< {"from":"server0-preference2","value":false}
>> {"type":"listAddons","iconDataURL":true,"to":"root"}
>> {"type":"listTabs","to":"root"}
<< {"from":"root","addons":[]}
<< {"from":"root","tabs":[{"actor":"server0-tab4","title":"xkcd: Atom","url":"https://xkcd.com/","browserId":1,"browsingContextID":1,"outerWindowID":1,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}
>> {"type":"getFavicon","to":"server0-tab4"}
<< {"from":"server0-tab4","favicon":null}
>> {"type":"listWorkers","to":"root"}
<< {"from":"root","workers":[]}
>> {"type":"listProcesses","to":"root"}
<< {"from":"root","processes":[{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}]}
>> {"type":"getProcess","id":0,"to":"root"}
<< {"from":"root","processDescriptor":{"actor":"server0-process3","id":0,"isParent":true,"isWindowlessParent":false,"traits":{"watcher":true,"supportsReloadDescriptor":true}}}
>> {"type":"listServiceWorkerRegistrations","to":"root"}
<< {"from":"root","registrations":[]}