From 46f41dfc65d105431b51a4e09c6c6318e65b6545 Mon Sep 17 00:00:00 2001 From: meh Date: Sat, 15 Aug 2026 21:08:13 +0700 Subject: [PATCH 1/2] feat(browser): show a page's source with view-source: `view-source:https://example.com` fetches what it names and shows the bytes instead of rendering them. No new URL parsing. `view-source:https://x` already parses as a URL whose scheme is `view-source` and whose path is the inner address, so `request_from_input` accepts it, the address bar shows it, and history holds it like any other entry. Opening it in a new tab is the existing `open_tab` command with that address; nothing here needs to know whether it is a new tab or the current one. The source is escaped and put in a `
`, and that is the whole job.
Nothing may reformat, pretty-print or re-serialise it: a source view that
showed a parsed and re-emitted tree would be answering a different
question. For a page whose claim is "there is no script here" it would be
the wrong answer, because the reader is checking the bytes rather than
the tree.

The test pins the properties that matter: the script tag survives with
its type readable, markup is escaped rather than interpolated, and
ampersands are escaped before angle brackets -- the other order turns
`<` into `&lt;` and quietly corrupts every escape on the page.
---
 apps/chuzz/src/browser.rs | 74 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/apps/chuzz/src/browser.rs b/apps/chuzz/src/browser.rs
index 582ceab6..05ab4c07 100644
--- a/apps/chuzz/src/browser.rs
+++ b/apps/chuzz/src/browser.rs
@@ -127,6 +127,8 @@ enum PageSource {
     },
     /// Built by a WebAssembly guest. Nothing is fetched and no JavaScript runs.
     Wasm { module: std::path::PathBuf },
+    /// The bytes a server sent, shown rather than rendered.
+    Source { text: String },
 }
 
 /// A module a page declared, fetched alongside it.
@@ -412,6 +414,12 @@ impl Browser {
                         .unwrap_or_default();
                     (Box::new(page), title)
                 }
+                PageSource::Source { text } => {
+                    let page =
+                        blitz_html::HtmlDocument::from_html(&source_html(&text), make_config())
+                            .into_inner();
+                    (Box::new(page), format!("source of {}", bundle.resolved_url))
+                }
                 PageSource::Wasm { module } => match build_wasm_page(&module, make_config()) {
                     // A bare `BaseDocument`, not a `ScriptDocument`. The mount
                     // takes `Box` and `BaseDocument` implements
@@ -647,6 +655,24 @@ async fn fetch_page_module(
     })
 }
 
+/// Wrap a server's bytes in the smallest document that shows them verbatim.
+///
+/// Escaped and put in a `
`, which is the whole job: the point of view
+/// source is that what you read is what arrived, so nothing here may reformat,
+/// pretty-print or re-serialise it. A document that showed a parsed and
+/// re-emitted tree would be answering a different question, and for a page
+/// whose claim is "there is no script here" it would be the wrong answer.
+fn source_html(text: &str) -> String {
+    let escaped = text
+        .replace('&', "&")
+        .replace('<', "<")
+        .replace('>', ">");
+    format!(
+        r#"Source
+
{escaped}
"# + ) +} + pub(crate) fn page_node(document: &blitz_dom::BaseDocument, tab_id: u64) -> Option { if let Some(node) = document.get_element_by_id(&format!("chuzz-page-{tab_id}")) { return Some(node); @@ -694,6 +720,25 @@ async fn fetch_page( }, }; } + // `view-source:` is answered by fetching what it names and showing the + // bytes instead of rendering them. The prefix stays on the tab's URL, so + // history and the address bar hold it like any other address. + if request.url.scheme() == "view-source" { + let inner = request.url.path().to_owned(); + let source = match Url::parse(&inner) { + Ok(url) => match net.fetch_async(Request::get(url)).await { + Ok((_, bytes)) => decode_body(&bytes), + Err(error) => format!("could not fetch {inner}: {error:?}"), + }, + Err(error) => format!("{inner} is not a URL: {error}"), + }; + return PageBundle { + tab_id, + generation, + resolved_url: request.url.to_string(), + source: PageSource::Source { text: source }, + }; + } if force_revalidate { revalidate(&mut request); } @@ -1169,6 +1214,35 @@ mod tests { ); } + /// What you read has to be what arrived. For a page whose whole claim is + /// "there is no script here", a source view that re-serialised a parsed + /// tree would be answering a different question. + #[test] + fn view_source_shows_the_bytes_verbatim() { + let page = r##"

fallback

+"##; + let shown = source_html(page); + + // The tag survives, escaped, so a reader can see there is exactly one + // script and what type it is. + assert!(shown.contains("<script type=\"application/wasm\"")); + assert!(shown.contains("mount=\"#root\">")); + + // Escaped, not executed or parsed away. + assert!( + !shown.contains("
"), + "the source was interpolated as markup instead of escaped" + ); + assert!(shown.contains("<div id=\"root\">")); + + // Ampersands first, or `<` would come back out as `&lt;`. + assert_eq!( + source_html("a & b < c").matches("&").count(), + 1, + "escaping order mangles ampersands" + ); + } + #[test] fn browser_starts_with_one_blank_tab() { let browser = Browser::new(None); From e2341c1abdd56e19c8b2ec7d51ec65e1bcd57355 Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 16 Aug 2026 05:17:01 +0700 Subject: [PATCH 2/2] feat(shortcuts): open the source of the current page with Cmd-U The scheme existed and the only way to reach it was to type it. Cmd-U is what Chrome and Safari bind, and it opens a new tab rather than replacing the page, so the source and the page it came from stay side by side. Inert on a tab that is already showing source: the naive version opens view-source:view-source:https://..., which the address bar accepts and nothing can render. --- apps/chuzz/frontend/src/lib/shortcuts.ts | 5 ++++- apps/chuzz/frontend/src/stores/browser.tsx | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/chuzz/frontend/src/lib/shortcuts.ts b/apps/chuzz/frontend/src/lib/shortcuts.ts index c53af265..1306ed1a 100644 --- a/apps/chuzz/frontend/src/lib/shortcuts.ts +++ b/apps/chuzz/frontend/src/lib/shortcuts.ts @@ -6,7 +6,8 @@ export type BrowserShortcut = | "reload" | "focus-address" | "back" - | "forward"; + | "forward" + | "view-source"; export interface ShortcutInput { key: string; @@ -47,6 +48,8 @@ export function resolveBrowserShortcut(event: ShortcutInput): BrowserShortcut | if (key === "l" || key === "d" || event.code === "KeyL" || event.code === "KeyD") { return "focus-address"; } + // Cmd-U, the same binding Chrome and Safari use. + if (key === "u" || event.code === "KeyU") return "view-source"; if (key === "[" || event.code === "BracketLeft") return "back"; if (key === "]" || event.code === "BracketRight") return "forward"; return undefined; diff --git a/apps/chuzz/frontend/src/stores/browser.tsx b/apps/chuzz/frontend/src/stores/browser.tsx index ef1e7684..82bc61d7 100644 --- a/apps/chuzz/frontend/src/stores/browser.tsx +++ b/apps/chuzz/frontend/src/stores/browser.tsx @@ -108,6 +108,16 @@ function createBrowserStore() { case "focus-address": focusAddress(); break; + case "view-source": { + const url = state.tabs.find((tab) => tab.id === state.activeTabId)?.url; + // Inert on a source tab rather than recursive. The naive version + // opens `view-source:view-source:https://...`, which the address bar + // happily accepts and nothing can render. + if (url && !url.startsWith("view-source:")) { + void api.openTab(`view-source:${url}`); + } + break; + } case "back": void api.goBack(state.activeTabId); break;