Repository navigation
Show each captured body in a viewer for its content type - #67
Merged
Merged
Conversation
The Network tab sent every body through the JSON highlighter, and showed it as plain text when it was not JSON. A form, an XML or HTML page, or a large JSON document was hard to read, and an image showed without its size. The detail now picks a viewer by the body's content type. JSON gets a tree that folds by object and array: a click on a key or a bracket folds the node to one line that counts its keys or items, and Alt-click also folds everything inside it. A form body is a table of its fields, with names and values decoded; a part that is not valid percent-encoding is shown as sent. XML and HTML are indented, with whitespace added and nothing else, as the JSON view does. For HTML, void elements, the end tags that HTML leaves out, and the text of script, style, pre, textarea, and title follow HTML's rules. PNG, JPEG, GIF, and WebP show as an image with their size and pixel dimensions. Other text has line numbers from CSS counters. A body of no known type that scans as JSON gets the tree, as a mocked body without a Content-Type does, and a JSON body cut at the capture cap shows as text. Each viewer has a Raw view: the body as captured with line numbers, or an image's bytes as a hex dump from the body route. The browser remembers the view chosen for each content type. Copy copies the body as captured in every view, so for JSON it no longer adds indentation; the Copy button of the whole transaction still does. Download saves the body from the body route. A search renders the detail again, and the tree keeps its folded nodes; a folded node that holds a match is highlighted. The renderers are in shared.js, next to the JSON highlighter, so any tab can use them, and shared.css has their .dc-tree, .dc-markup, .dc-lines, and .dc-kv components and .dc-seg--sm, documented in docs/STYLEGUIDE.md. Folding is one delegated click listener in shared.js. The renderers escape all of a body's text. The node tests pin that only their own tags come out for markup payloads in bodies and in search queries, that the tree and the markup printer add whitespace and nothing else, and that the markup printer stays linear on input built to make it quadratic. The sample has a request for each viewer, and one that posts a 250 KB JSON body. Verified against the debug sample on a physical device, with the console in desktop Chrome, in the dark and the light theme. Each request got its viewer, Raw and Copy worked in each viewer, and the view was remembered for its content type only. On the same computer, best of five runs, building and laying out the tree for the 250 KB JSON body took 118 to 138 ms, and the previous highlighter took 101 to 111 ms on the same body. Raw took 8 ms for that body, and 140 ms for the same body indented, which is 33,000 lines. Signed-off-by: Evgenii Plokhov <plokhov@gmail.com>
The markup printer trimmed the whitespace at the ends of each run of text, so <value> padded </value> showed as <value>padded</value>. That whitespace is part of the value, and the view is meant to add whitespace and nothing else. It also put each piece of mixed content on its own line, so <item>Why <em>W</em> are great</item> took five lines. The printer now builds the elements first, with the same HTML rules as before. An element that holds text, a CDATA section, or nothing but whitespace is printed as it arrived, on the line of its start tag. Only an element that holds elements, comments, and the like gets a line for each of them, and there the whitespace between them gives way to the printer's. A tag cut off at the end of the body is a separate token now, so it does not make the element around it look like text. Tests pin both cases, and a generated set of documents checks that the exact source of every element that holds text is in the output. Verified in the console against the debug sample on a physical device: the slideshow XML keeps each mixed-content item on one line, and the HTML page's paragraph keeps its own line breaks. Signed-off-by: Evgenii Plokhov <plokhov@gmail.com>
The sample got every image from a response, and only in the types that the public fixture serves: PNG, JPEG, WebP, and SVG. So two paths of the body viewers had no request that reaches them: an image in a request body, and an image type that the console does not preview. Two buttons in the Body viewers section now post a 96 x 64 image to /anything. One is a PNG that Android encodes. The other is a 24-bit BMP that the sample writes itself, since Android has no BMP encoder. Verified on a physical device with the console in desktop Chrome. Both bodies were captured as bytes, 1,238 and 18,486 bytes. In the Request tab, the PNG showed its preview with "1.2 KB · 96 × 64 px", and in Raw a hex dump that starts with the PNG signature. The BMP showed no image, the note that image/bmp has no preview, and a hex dump that starts with "BM", and its download is named for a .bmp file. Signed-off-by: Evgenii Plokhov <plokhov@gmail.com>
…tion A review of the body viewers found four things to fix. JSON sent with another content type got no JSON view. Servers send JSON as text/html, and apps send it as a form, and the highlighter that this change replaced indented every body that was JSON. A body that is a valid JSON object or array now gets the tree whatever its type says; the type still decides when the body is not JSON. JSON.parse answers that question, since it accepts the grammar debugScanJsonSource does. A selection copied from line-numbered text lost every empty line, because each row was a block with no line break in it. Each row now ends with its line break. The rows render at the same height, and a selection copies the text as from a plain <pre>, which leaves out only the final line break. A search that crossed the parts of a tag, such as type="all" or <title, got no mark in the XML and HTML view. Each tag is now searched as a whole, and the mark is split across the spans of its parts. The items of .dc-seg--sm are 18 px tall, under the styleguide's 30 px for a hit target. An invisible area above and below each item now takes the clicks, as the master toggle and the rule switch do. Tests pin the four. Verified on an emulator with mocked responses: a JSON body sent as text/html got the tree, a selection copied from an event stream with three empty lines equals the body without its final line break, a search for type="all" marked both slides of an XML body, and clicks 5 px above and below a view-switch item reached it. Signed-off-by: Evgenii Plokhov <plokhov@gmail.com>
The detail kept the folded nodes of its JSON trees through a render only when the request and the response body were both unchanged. While a response streams, the detail renders again at least every 750 ms with a longer response body, so the folds of the request's tree were lost on each render. A POST with a JSON body and an event-stream response, as a chat completion API sends, hit this on every request. The folds of each direction are now kept when that direction's body is unchanged. Verified against the debug sample on a physical device, with the page's fetch changed so the poll reports a POST with a JSON body as streaming and each detail fetch returns a longer response body. Before the change, the request's folded root was open again after three renders; after it, the root stayed folded through the renders and a search. Signed-off-by: Evgenii Plokhov <plokhov@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Network tab sent every body through the JSON highlighter, and showed it as plain text when it was not JSON. Now that the wire carries each body's content type and the body route serves any captured body, the detail picks a viewer by the content type.
*/json,*+json, or a valid JSON object or array under any other typeapplication/x-www-form-urlencoded*/xmland*+xml, SVG includedtext/htmlTree. A click on a key or a bracket folds the node to one line that counts its keys or items, and Alt-click also folds everything inside it. Search marks appear inside the nodes, a folded node that holds a match is highlighted, and the folds of a body survive the renders that a search or a refresh causes while that body stays the same, such as a request's while its response streams. A click that ends a text selection does not fold, so keys can still be selected.
Markup printer. Every tag, comment, CDATA section, and run of text is the exact slice of the input. An element that holds text, even in part, is shown as it arrived, because its whitespace is part of the text:
<value> padded </value>and<item>Why <em>W</em> are great</item>stay as they are. An element that holds only elements gets one line for each, and the whitespace between them gives way to the printer's indentation. For HTML: void elements, the end tags HTML leaves out (li,p,dt/dd,td/th,tr, table sections,option), and the text ofscript,style,pre,textarea, andtitle, which is not markup. A search is marked across the parts of a tag, sotype="all"and<titleare found. A body cut inside a tag ends with that tag on its own line. Indentation stops at 32 levels, so a body full of unclosed tags cannot make the output grow with the square of its size.Raw, Copy, and Download. Each viewer has a Raw view, and the browser remembers the view chosen for each content type (
debug-network-body-viewsin localStorage, like the headers toggle). Each row of the line-numbered text keeps its line break, so a selection copied from it keeps its empty lines. Copy copies the body as captured in every view. Download saves the body from the body route.Where the code is. The renderers are pure functions in
shared.js, next to the JSON highlighter, so the node tests can load them and any tab can use them:debugBodyKind,debugJsonTree,debugHighlightMarkup,debugLineNumbered,debugFormTable, anddebugHexDump.shared.csshas their components (.dc-tree,.dc-markup,.dc-lines,.dc-kv, and.dc-seg--sm), anddocs/STYLEGUIDE.mddocuments them. Folding is one delegated click listener inshared.js, and the view switch is adata-actioninnetwork.js, so there is no inline handler.network.jspicks the viewer, builds the bar above the body, and loads the hex dump and the image size after render. The sample gets a "Body viewers" section with a request for each viewer, a PNG and a BMP that it posts as request bodies (no public fixture serves an image type the console cannot preview), and a request that posts a 250 KB JSON body.Decisions to check
Verification
On a physical device, against the debug sample, with the console in desktop Chrome, in the dark and the light theme:
Each sample request got its viewer:
GET /jsonthe tree,GET /xmlandGET /image/svgthe XML printer,GET /htmlthe HTML printer,GET /image/jpegandGET /image/pngthe image (34.8 KB · 239 × 178 px),POST a formthe table (thepasswordfield shows the redactor's[REDACTED]),GET /robots.txtline numbers. A mocked response without a Content-Type got the tree.Raw worked in each viewer. Copy wrote exactly the captured body (compared with the detail's
responseBody), and in the image's Raw view exactly the hex dump on screen. The JPEG's dump has 2,225 lines.Switching XML to Raw was remembered for
application/xmland did not change JSON.The sample's new
POST a PNGandPOST a BMPbuttons send a 96 × 64 image as a request body, and capture kept both as bytes. In the Request tab, the PNG showed its preview (1.2 KB · 96 × 64 px) and, in Raw, a hex dump that starts with the PNG signature. The BMP showed no<img>, the note "image/bmp has no preview", and a hex dump that starts withBM; its download is namedlustro-<id>-request.bmp.The 250 KB JSON body: Alt-click on
itemsand then a click folded all 1,750 items to one line each (6,621 of 6,623 nodes folded). A search for one item's name marked it, kept the folds, and highlighted the folded item that holds it.Render time on the same computer, best of five runs, from setting
innerHTMLuntil layout:On a physical device, with live traffic for the event stream and the XML, the review fixes held: a selection copied from the rows of a live event stream (5 events, 5 empty lines) equals the body without its final line break, and a search for
type="all"marked both slides of the XML. The same checks passed on an emulator with mocked responses, where a JSON body sent astext/htmlgot the tree; a selection copied from the rows of an event stream with three empty lines equals the body, without its final line break, as from a plain<pre>; a search fortype="all"marked both slides of the XML; and the items of the small view switch take clicks 5 px above and below them.node --test lustro/src/test/js/*.test.js: 86 tests pass. A mutation check removed the escaping from each renderer in turn, the prototype-free tag tables, the depth limit, the guard for a top-level scalar, and the rules that keep an element's text as it arrived, and each one failed tests../gradlew detekt :lustro:testDebugUnitTest :sample:testDebugUnitTest :lustro:lintDebug :sample:lintDebugpasses.InlineHandlerCspTestcovers the changed assets.Type of change
Checklist
./gradlew apiCheckpasses (no unintended public API changes; dump updated if intended): no Kotlin API changes, only the sample'sMainActivity./gradlew detektpassesCHANGELOG.mdupdated under[Unreleased]wire-protocol/and tab OpenAPI fragments: nonegit commit -sCompatibility notes
No wire-protocol or Kotlin API change. The new
.dc-*components andshared.jsfunctions add to the tab-author contract and change nothing that exists. The body's Copy button copies the exact captured text, also for JSON (see decision 1).