From 1146b90f4470b9863c6f670b1a8d93888070edb3 Mon Sep 17 00:00:00 2001 From: Marcos Caceres Date: Tue, 1 Sep 2026 16:47:37 +1000 Subject: [PATCH 1/2] refactor(core): route ReSpec's own style injection through insertStyle --- src/core/algorithms.js | 5 ++--- src/core/caniuse.js | 13 +++++-------- src/core/cddl.js | 12 ++++-------- src/core/data-type.js | 5 ++--- src/core/dfn-index.js | 5 ++--- src/core/dfn-panel.js | 8 ++------ src/core/examples.js | 8 ++------ src/core/highlight-vars.js | 5 ++--- src/core/highlight.js | 8 ++------ src/core/implementation-status.js | 13 +++++-------- src/core/insert-style.js | 29 +++++++++++++++++++++++++++++ src/core/issues-notes.js | 9 ++------- src/core/mdn-annotation.js | 5 ++--- src/core/style.js | 16 ++-------------- src/core/ui.js | 12 ++---------- src/core/webidl.js | 7 ++++--- 16 files changed, 69 insertions(+), 91 deletions(-) create mode 100644 src/core/insert-style.js diff --git a/src/core/algorithms.js b/src/core/algorithms.js index 9a46608be5..2867b13cd0 100644 --- a/src/core/algorithms.js +++ b/src/core/algorithms.js @@ -4,6 +4,7 @@ Currently used only for adding 'assert' class to algorithm lists */ import css from "../styles/algorithms.css.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/algorithms"; @@ -32,7 +33,5 @@ export function run() { } } - const style = document.createElement("style"); - style.textContent = css; - document.head.appendChild(style); + insertStyle(css); } diff --git a/src/core/caniuse.js b/src/core/caniuse.js index 4b7b357f9a..aa796b325c 100644 --- a/src/core/caniuse.js +++ b/src/core/caniuse.js @@ -11,6 +11,7 @@ import { codedJoinAnd, docLink, showError, showWarning } from "./utils.js"; import { pub, sub } from "./pubsubhub.js"; import css from "../styles/caniuse.css.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/caniuse"; @@ -59,14 +60,10 @@ export function prepare(conf) { return; // no feature to show } - document.head.appendChild( - html`` - ); + insertStyle(css, { + id: "caniuse-stylesheet", + className: options.removeOnSave ? "removeOnSave" : "", + }); } /** * @param {string} browser diff --git a/src/core/cddl.js b/src/core/cddl.js index 9aa929d6af..4d6e242dff 100644 --- a/src/core/cddl.js +++ b/src/core/cddl.js @@ -14,6 +14,7 @@ import { import { addHashId, showError, showWarning, xmlEscape } from "./utils.js"; import { createCopyButton, injectCopyScript } from "./clipboard.js"; import css from "../styles/cddl.css.js"; +import { insertStyle } from "./insert-style.js"; import { registerDefinition } from "./dfn-map.js"; import { sub } from "./pubsubhub.js"; @@ -703,14 +704,9 @@ export async function run() { if (!cddls.length) return; // Inject CSS - const style = document.createElement("style"); - style.textContent = css; - const styleAnchor = document.querySelector("head link, head > *:last-child"); - if (styleAnchor) { - styleAnchor.before(style); - } else { - document.head.append(style); - } + insertStyle(css, { + before: document.querySelector("head link, head > *:last-child"), + }); // Import cddlparser via import-maps (avoids fs/path imports in main entry) const parse = (/** @type {string} */ text) => { diff --git a/src/core/data-type.js b/src/core/data-type.js index 0e530297fc..c9abe92c33 100644 --- a/src/core/data-type.js +++ b/src/core/data-type.js @@ -6,6 +6,7 @@ * Set `conf.highlightVars = true` to enable. */ import css from "../styles/datatype.css.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/data-type"; @@ -17,9 +18,7 @@ export function run(conf) { return; } - const style = document.createElement("style"); - style.textContent = css; - document.head.appendChild(style); + insertStyle(css); let section = null; const varMap = new Map(); diff --git a/src/core/dfn-index.js b/src/core/dfn-index.js index 2a00414ce0..2a631f1cab 100644 --- a/src/core/dfn-index.js +++ b/src/core/dfn-index.js @@ -10,6 +10,7 @@ import { biblio } from "./biblio.js"; import css from "../styles/dfn-index.css.js"; import { getTermFromElement } from "./xref.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; import { renderInlineCitation } from "./render-biblio.js"; import { sub } from "./pubsubhub.js"; import { toCiteDetails } from "./data-cite.js"; @@ -103,9 +104,7 @@ export function run() { return; } - const styleEl = document.createElement("style"); - styleEl.textContent = css; - document.head.appendChild(styleEl); + insertStyle(css); index.classList.add("appendix"); if (!index.querySelector("h2, h1")) { diff --git a/src/core/dfn-panel.js b/src/core/dfn-panel.js index 358d89ccdd..402c5d5fda 100644 --- a/src/core/dfn-panel.js +++ b/src/core/dfn-panel.js @@ -5,17 +5,13 @@ import css from "../styles/dfn-panel.css.js"; import { fetchBase } from "./text-loader.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; import { norm } from "./utils.js"; export const name = "core/dfn-panel"; export async function run() { - document.head.insertBefore( - html``, - document.querySelector("link") - ); + insertStyle(css, { before: document.querySelector("link") }); /** @type {NodeListOf} */ const elems = document.querySelectorAll( diff --git a/src/core/examples.js b/src/core/examples.js index 9b48cdc3c7..8aa205fece 100644 --- a/src/core/examples.js +++ b/src/core/examples.js @@ -9,6 +9,7 @@ import { addId, getIntlData } from "./utils.js"; import css from "../styles/examples.css.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/examples"; @@ -75,12 +76,7 @@ export function run() { ); if (!examples.length) return; - document.head.insertBefore( - html``, - document.querySelector("link") - ); + insertStyle(css, { before: document.querySelector("link") }); let number = 0; examples.forEach(example => { diff --git a/src/core/highlight-vars.js b/src/core/highlight-vars.js index 537a1328a7..3ece135a23 100644 --- a/src/core/highlight-vars.js +++ b/src/core/highlight-vars.js @@ -9,6 +9,7 @@ */ import css from "../styles/var.css.js"; import { fetchBase } from "./text-loader.js"; +import { insertStyle } from "./insert-style.js"; import { sub } from "./pubsubhub.js"; export const name = "core/highlight-vars"; @@ -21,9 +22,7 @@ export async function run(conf) { return; } - const styleElement = document.createElement("style"); - styleElement.textContent = css; - document.head.appendChild(styleElement); + insertStyle(css); const script = document.createElement("script"); script.id = "respec-highlight-vars"; diff --git a/src/core/highlight.js b/src/core/highlight.js index 176fda085a..0ff368e5cd 100644 --- a/src/core/highlight.js +++ b/src/core/highlight.js @@ -5,7 +5,7 @@ * Performs syntax highlighting to all pre and code elements. */ import css from "../styles/highlight.css.js"; -import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; import { msgIdGenerator } from "./utils.js"; import { workerPromise } from "./worker.js"; export const name = "core/highlight"; @@ -107,10 +107,6 @@ export async function run(conf) { const promisesToHighlight = highlightables .filter(elem => elem.textContent.trim()) .map(highlightElement); - document.head.appendChild( - html`` - ); + insertStyle(css); await Promise.all(promisesToHighlight); } diff --git a/src/core/implementation-status.js b/src/core/implementation-status.js index e56d3a200d..2a06100a4f 100644 --- a/src/core/implementation-status.js +++ b/src/core/implementation-status.js @@ -12,6 +12,7 @@ import { docLink, fetchAndCache, showWarning } from "./utils.js"; import { pub, sub } from "./pubsubhub.js"; import css from "../styles/implementation-status.css.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/implementation-status"; @@ -165,14 +166,10 @@ export function prepare(conf) { const options = /** @type {ImplementationStatusOptions} */ ( conf.implementationStatus ); - document.head.appendChild( - html`` - ); + insertStyle(css, { + id: "baseline-stylesheet", + className: options.removeOnSave ? "removeOnSave" : "", + }); } /** @param {RespecConfig} conf */ diff --git a/src/core/insert-style.js b/src/core/insert-style.js new file mode 100644 index 0000000000..b0b0233c60 --- /dev/null +++ b/src/core/insert-style.js @@ -0,0 +1,29 @@ +// @ts-check +// Module core/insert-style +// One place where ReSpec adds its own ``, - headElem.querySelector("link") - ); + insertStyle(css, { before: document.head.querySelector("link") }); handleIssues(issuesAndNotes, ghIssues, conf); const ednotes = document.querySelectorAll(".ednote"); ednotes.forEach(ednote => { diff --git a/src/core/mdn-annotation.js b/src/core/mdn-annotation.js index cfecc784f5..35f7f32592 100644 --- a/src/core/mdn-annotation.js +++ b/src/core/mdn-annotation.js @@ -2,6 +2,7 @@ import { docLink, fetchAndCache, getIntlData, showError } from "./utils.js"; import css from "../styles/mdn-annotation.css.js"; import { html } from "./import-maps.js"; +import { insertStyle } from "./insert-style.js"; export const name = "core/mdn-annotation"; @@ -152,9 +153,7 @@ export async function run(conf) { return; } - const style = document.createElement("style"); - style.textContent = css; - document.head.append(style); + insertStyle(css); for (const elem of findElements(mdnSpecJson)) { const mdnSpecArray = mdnSpecJson[elem.id]; diff --git a/src/core/style.js b/src/core/style.js index 8f3c4c2029..dd710b1bc7 100644 --- a/src/core/style.js +++ b/src/core/style.js @@ -12,22 +12,10 @@ export const name = "core/style"; import css from "../styles/respec.css.js"; +import { insertStyle } from "./insert-style.js"; // Opportunistically inserts the style to reduce some FOUC. -/** @type {HTMLStyleElement} */ -const styleElement = insertStyle(); - -/** - * Inserts the ReSpec CSS as a `style` element into the document's `head`. - * @return {HTMLStyleElement} The `style` element that was inserted. - */ -function insertStyle() { - const styleElement = document.createElement("style"); - styleElement.id = "respec-mainstyle"; - styleElement.textContent = css; - document.head.appendChild(styleElement); - return styleElement; -} +const styleElement = insertStyle(css, { id: "respec-mainstyle" }); /** * Removes the ReSpec CSS if the `noReSpecCSS` configuration option is `true`. diff --git a/src/core/ui.js b/src/core/ui.js index 7a6ebaac9b..b56545cfa9 100644 --- a/src/core/ui.js +++ b/src/core/ui.js @@ -11,21 +11,13 @@ import { html, pluralize } from "./import-maps.js"; import { reindent, xmlEscape } from "./utils.js"; import css from "../styles/ui.css.js"; +import { insertStyle } from "./insert-style.js"; import { markdownToHtml } from "./markdown.js"; import { sub } from "./pubsubhub.js"; export const name = "core/ui"; // Opportunistically inserts the style, with the chance to reduce some FOUC -insertStyle(); - -function insertStyle() { - const styleElement = document.createElement("style"); - styleElement.id = "respec-ui-styles"; - styleElement.textContent = css; - styleElement.classList.add("removeOnSave"); - document.head.appendChild(styleElement); - return styleElement; -} +insertStyle(css, { id: "respec-ui-styles", className: "removeOnSave" }); /** * @param {Element | null | undefined} elem diff --git a/src/core/webidl.js b/src/core/webidl.js index fdc1f8ec2e..ed21eb83f1 100644 --- a/src/core/webidl.js +++ b/src/core/webidl.js @@ -16,6 +16,7 @@ import { decorateDfn, findDfn } from "./dfn-finder.js"; import { html, webidl2 } from "./import-maps.js"; import { addCopyIDLButton } from "./webidl-clipboard.js"; import css from "../styles/webidl.css.js"; +import { insertStyle } from "./insert-style.js"; import { registerDefinition } from "./dfn-map.js"; export const name = "core/webidl"; @@ -384,9 +385,9 @@ export async function run() { if (!idls.length) { return; } - const style = document.createElement("style"); - style.textContent = css; - document.querySelector("head link, head > *:last-child").before(style); + insertStyle(css, { + before: document.querySelector("head link, head > *:last-child"), + }); const astArray = [...idls].map(renderWebIDL); From 3ab8dc9544252d23300d27287ee7e3232b760286 Mon Sep 17 00:00:00 2001 From: Marcos Caceres Date: Thu, 3 Sep 2026 00:50:20 +1000 Subject: [PATCH 2/2] fix(core/insert-style): insert through head so an outside anchor cannot move the style --- src/core/insert-style.js | 11 ++-- tests/unit/core/insert-style-spec.js | 77 ++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 tests/unit/core/insert-style-spec.js diff --git a/src/core/insert-style.js b/src/core/insert-style.js index b0b0233c60..20ba2870bf 100644 --- a/src/core/insert-style.js +++ b/src/core/insert-style.js @@ -1,7 +1,6 @@ // @ts-check // Module core/insert-style -// One place where ReSpec adds its own `