Repository navigation
several fixes related to hash urls - #139
Merged
Merged
Conversation
The scramjet harness, its wisp server and the bare harness were fixed at 4500-4502, so two runway instances (or anything else on those ports) could not run side by side. RUNWAY_PORT_BASE moves all three, and the harness page finds wisp next to its own port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MJ6gCzt6WZNrhpy8GvnNb
…s in the document
The fragment went through the URL codec, so the real URL's fragment was
not the site's: `#a/b` became `#a%2Fb`, which broke `:target`, native
scroll-to-anchor, text fragments and `location.hash` round trips. It is
never sent anywhere, so it now stays as written. The JS rewriter
encoded it into static import URLs too, so `import "./m.js#a"` and
`import("./m.js#a")` could load two instances.
In-page `#x` navigations reloaded the document: rewritten URLs carry
the navigator's query params (`$io`, ...) and the document's real URL
carries its loader's, so the browser never saw the same document. A
navigation to the document's own URL (links, location setters, assign,
replace, `window.open(url, "_self")`, meta refresh, controller.go) now
goes to its real URL with the new fragment, and the browser applies its
own fragment-navigation and replace rules. Fragment-only hyperlinks are
left relative, so they resolve when followed - after pushState too.
That relies on the real <base href> being a proxy URL, which it was
not: anything left relative resolved against the real site. It is now
rewritten (ignored `data:`/`javascript:` bases are left for the browser
to ignore). The client's base URL resolved a relative base against the
origin rather than the document URL; only the first base element with
an href counts; about:blank and srcdoc documents fall back to their
creator's base URL.
History: pushState/replaceState implement "can have its URL
rewritten" rather than comparing origins, resolve against the base URL,
treat "" like null as Chrome does, and keep the document's own request
params on the real entry URL, so a frame reloads as a frame.
Also: CSS `url(#id)` is left alone (gradients, clip paths, masks,
filters), blob URLs keep their fragment (`#t=5`, `#page=2`), window
handler content attributes (onhashchange, onpopstate, onmessage, ...)
are rewritten instead of running against the real globals, and
`_top`/`_parent` target keywords are matched case-insensitively.
Tests: runway fragments.ts (end to end against bare Chrome, reached
cross-origin the way real pages are) and rewriter-fragments.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MJ6gCzt6WZNrhpy8GvnNb
…n, file: history
- A fragment put back into a static import's string literal is escaped
for one. The fragment percent-encode set leaves `\` and `'`, so
`import "./m.js#a\\"` came out as invalid JavaScript. The module's own
URL had the same problem in `$scramjet$meta(import.meta, "...")` and
`$scramjet$import("...", ...)`: the service worker sees the fragment a
module was requested with, and the rewriter put it in unescaped. Both
now go through one escape function, and Flags carries the escaped base.
- Only an HTML base element in the document's own tree sets the base URL.
The service worker adopted the first `<base>` it saw, including one in
a <template> or an SVG/MathML element named base, so later scripts
loaded from the wrong directory. The client matched `base[href]`, which
also finds the SVG one; it now checks the namespace.
- A navigation to the document's own URL only reuses its real URL when it
has a fragment - when it stays in the document. Without one it loads a
new document, and reusing the real URL kept the initiator of whoever
first loaded it (`$io`), so `location.assign(location.href)` after
entering from another origin was sent as Sec-Fetch-Site: same-site. The
cost is that such a navigation pushes a history entry where the browser
replaces one (fragment-self-navigation-replaces-entry, listed failing).
A <base> naming the document still resolves to its real URL, since it
loads nothing itself.
- "can have its URL rewritten" lets a file: URL change its query, as HTML
says; it fell through to the fragment-only rule. Moved out of the
history interceptor so it can be tested on its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MJ6gCzt6WZNrhpy8GvnNb
Whether a <base> sets the document's base URL depends on it being an HTML element, and the rewriter guessed that from ancestor names. The parser's own foreign-content tracking made the same guess: `title`, `mi` and `annotation-xml` counted as integration points in any namespace, `annotation-xml`'s `encoding` was never read, and breakout start tags (`<p>`, `<div>`, `<font color>` ... inside <svg>) did not end foreign content. So `<math><annotation-xml><base>` set the base URL, and so did a base in an HTML-looking spot that Chrome puts in SVG; an SVG element named `template` was taken for an inert HTML one. The parser now decides each element's namespace by the tree construction rules - MathML text and HTML integration points, `annotation-xml` by its encoding, breakout tags including `font` with color/face/size - and DomBuilder records it on the Element. The tokenizer's raw-text, CDATA and self-closing decisions read the same state, and only an HTML element is void (an SVG `base` has children). The base check is now `namespace === "html"` outside an HTML template. Parser tests pin namespaces to the trees Chrome builds for the same markup; runway covers the base cases both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016MJ6gCzt6WZNrhpy8GvnNb
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.
No description provided.