mirror of
https://github.com/fergalmoran/ladybird.git
synced 2026-02-17 05:06:46 +00:00
326 lines
31 KiB
Markdown
326 lines
31 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
./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:
|
|
|
|
<img src="Images/devtools_setup.png" alt="DevTools setup" width="675px" />
|
|
|
|
This will make the address appear on the left-side of the page. Click "Connect":
|
|
|
|
<img src="Images/devtools_connect.png" alt="DevTools connect" width="240px" />
|
|
|
|
Once connected, the listed address will now be clickable itself. Click it to be brought to a page which shows Ladybird's
|
|
tab list:
|
|
|
|
<img src="Images/devtools_tab_list.png" alt="DevTools tab list" width="675px" />
|
|
|
|
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:
|
|
|
|
<img src="Images/devtools_dom_tree.png" alt="DevTools DOM tree" width="675px" />
|
|
|
|
## DevTools protocol
|
|
|
|
Firefox's documentation of its [DevTools protocol](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html)
|
|
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"](https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#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:
|
|
|
|
```bash
|
|
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](../Libraries/LibDevTools/Actors/RootActor.cpp).
|
|
The root actor initiates the session by sending a message to the client describing itself:
|
|
|
|
```jsonc
|
|
<< {"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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/system.js)
|
|
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:
|
|
|
|
```jsonc
|
|
>> {"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`:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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](../Libraries/LibDevTools/DevToolsDelegate.h)
|
|
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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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.
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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](../Libraries/LibWeb/CSS/Properties.json).
|
|
|
|
(For brevity, only the `font` property is included here, as the list of all properties is very large.)
|
|
|
|
```jsonc
|
|
>> {"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.
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
>> {"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.
|
|
|
|
```jsonc
|
|
>> {"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.
|
|
|
|
```jsonc
|
|
>> {"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:
|
|
|
|
```jsonc
|
|
<< {"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":[]}
|
|
```
|