fix: inject CSS/script into body when head tag is missing#6
Conversation
pages like old.reddit.com start with <body> and have no <head> tag. our HeadInjector only fired on <head>, so these pages got no CSS text-transform, no uppercase script, no fetch/XHR patching — nothing. replaces HeadInjector with ScriptAndStyleInjector that registers on both head and body. uses an injected flag to prevent double injection. appends to head (normal), prepends to body (fallback). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes missing proxy behavior on HTML pages that omit a <head> element by injecting the proxy CSS/script into either <head> (normal) or <body> (fallback), while preventing double-injection when both tags exist.
Changes:
- Replace
HeadInjectorwith aScriptAndStyleInjectorthat runs on bothheadandbody. - Add an
injectedguard to ensure CSS/script are injected only once per document. - Add a regression test intended to detect double-injection on pages that have both
<head>and<body>.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/rewriter.ts | Adds shared injector registered on both head and body with a single-injection guard. |
| src/rewriter.test.ts | Adds a test attempting to ensure CSS/script aren’t injected twice. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| element(el: Element) { | ||
| // no <base> tag — it would redirect /browse/... paths to the target origin | ||
| // URLRewriter already resolves all relative URLs to absolute proxy paths | ||
| el.append( | ||
| `<style>*:not(input):not(textarea):not(select):not(code):not(pre):not(script):not(style) { text-transform: uppercase !important; } code, pre, textarea, svg { text-transform: none !important; }</style>`, | ||
| { html: true }, | ||
| ); | ||
| el.append(`<script>${uppercaseScript}</script>`, { html: true }); | ||
| if (this.injected) return; | ||
| this.injected = true; | ||
| // prepend into body (fallback for pages without <head>), append into head | ||
| const method = el.tagName === "head" ? "append" : "prepend"; | ||
| el[method](INJECTED_CSS, { html: true }); | ||
| el[method](`<script>${uppercaseScript}</script>`, { html: true }); |
There was a problem hiding this comment.
The new body-fallback injection path (pages without a ) isn’t covered by an automated test. Consider adding a deterministic integration/unit test that transforms a minimal HTML document starting with (no ) and asserts the CSS/script are injected (and placed in/at the start of body).
| const scriptCount = (html.match(/walkAndUppercase/g) || []).length; | ||
| // walkAndUppercase appears multiple times within the single script (definition + calls) | ||
| // but should NOT appear in a second duplicate script block | ||
| expect(scriptCount).toBeGreaterThan(0); | ||
| expect(scriptCount).toBeLessThan(10); |
There was a problem hiding this comment.
This test’s duplicate-script assertion is brittle: counting raw "walkAndUppercase" occurrences and bounding it (<10) will break if the script implementation changes (e.g., additional references). A more stable check is to assert a unique signature like "function walkAndUppercase" (or another sentinel) appears exactly once, or to count injected <script> blocks matching the injector output.
| const scriptCount = (html.match(/walkAndUppercase/g) || []).length; | |
| // walkAndUppercase appears multiple times within the single script (definition + calls) | |
| // but should NOT appear in a second duplicate script block | |
| expect(scriptCount).toBeGreaterThan(0); | |
| expect(scriptCount).toBeLessThan(10); | |
| const scriptDefCount = (html.match(/function walkAndUppercase\s*\(/g) || []).length; | |
| // Ensure the walkAndUppercase script is injected exactly once (no duplicate script blocks) | |
| expect(scriptDefCount).toBe(1); |
Summary
<head>tag — HTML starts directly with<body>. OurHeadInjectoronly fired on<head>, so these pages got zero proxy functionality: no CSS text-transform, no uppercase script, no fetch/XHR patching.HeadInjectorwithScriptAndStyleInjectorthat registers on both<head>and<body>injectedflag to prevent double injection on pages that have both tags<head>(normal case), prepends to<body>(fallback)Test plan
<head>) has exactly 1 CSS injection, no duplication🤖 Generated with Claude Code