diff --git a/Documentation/DevTools.md b/Documentation/DevTools.md new file mode 100644 index 0000000000..ecb4761687 --- /dev/null +++ b/Documentation/DevTools.md @@ -0,0 +1,325 @@ +# 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: + +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](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 `` +element, followed by requests for the children of the `` element, the document element, and the `` 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":[]} +``` diff --git a/Documentation/Images/devtools_connect.png b/Documentation/Images/devtools_connect.png new file mode 100644 index 0000000000..d8d1f96618 Binary files /dev/null and b/Documentation/Images/devtools_connect.png differ diff --git a/Documentation/Images/devtools_dom_tree.png b/Documentation/Images/devtools_dom_tree.png new file mode 100644 index 0000000000..469950975d Binary files /dev/null and b/Documentation/Images/devtools_dom_tree.png differ diff --git a/Documentation/Images/devtools_setup.png b/Documentation/Images/devtools_setup.png new file mode 100644 index 0000000000..718fb55b74 Binary files /dev/null and b/Documentation/Images/devtools_setup.png differ diff --git a/Documentation/Images/devtools_tab_list.png b/Documentation/Images/devtools_tab_list.png new file mode 100644 index 0000000000..a901a2757e Binary files /dev/null and b/Documentation/Images/devtools_tab_list.png differ