diff --git a/src/display/font_loader.js b/src/display/font_loader.js index b054f9a1f..2303a5b95 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -26,6 +26,8 @@ import { makePathFromDrawOPS } from "./display_utils.js"; class FontLoader { #systemFonts = new Set(); + #styleSheet = null; + constructor({ ownerDocument = globalThis.document, styleElement = null, // For testing only. @@ -55,14 +57,38 @@ class FontLoader { } insertRule(rule) { + const styleSheet = this.#getStyleSheet(); + styleSheet.insertRule(rule, styleSheet.cssRules.length); + } + + #getStyleSheet() { + if (this.#styleSheet) { + return this.#styleSheet; + } + + // Constructable stylesheets aren't blocked by CSP inline-style checks. + // Use the constructor from the document's own window, since + // `this._document` may belong to a different window (e.g. a print iframe) + // and a constructable stylesheet can only be adopted by the document it was + // created for. + const StyleSheet = + this._document.defaultView?.CSSStyleSheet || globalThis.CSSStyleSheet; + if (!this.styleElement && StyleSheet) { + const { adoptedStyleSheets } = this._document; + if (adoptedStyleSheets) { + const styleSheet = new StyleSheet(); + adoptedStyleSheets.push(styleSheet); + return (this.#styleSheet = styleSheet); + } + } + if (!this.styleElement) { this.styleElement = this._document.createElement("style"); this._document.documentElement .getElementsByTagName("head")[0] .append(this.styleElement); } - const styleSheet = this.styleElement.sheet; - styleSheet.insertRule(rule, styleSheet.cssRules.length); + return (this.#styleSheet = this.styleElement.sheet); } clear() { @@ -72,6 +98,16 @@ class FontLoader { this.nativeFontFaces.clear(); this.#systemFonts.clear(); + if (this.#styleSheet) { + const { adoptedStyleSheets } = this._document; + if (adoptedStyleSheets?.includes(this.#styleSheet)) { + this._document.adoptedStyleSheets = adoptedStyleSheets.filter( + styleSheet => styleSheet !== this.#styleSheet + ); + } + this.#styleSheet = null; + } + if (this.styleElement) { // Note: ChildNode.remove doesn't throw if the parentNode is undefined. this.styleElement.remove(); diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index 9e75a59b5..feaceea1c 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -1973,17 +1973,14 @@ describe("PDF viewer", () => { null, { earlySetup: () => { - // Capture state while window.print() runs — the print service's - // destroy() removes the @page stylesheet right after, on the - // afterprint event. + // Capture state during window.print(): destroy() removes the + // @page stylesheet from adoptedStyleSheets right afterwards. window._pageRuleApplied = null; window.print = () => { - window._pageRuleApplied = [ - ...document.querySelectorAll("style"), - ].some( + window._pageRuleApplied = document.adoptedStyleSheets.some( s => - s.sheet?.cssRules.length > 0 && - [...s.sheet.cssRules].some(r => r.cssText.includes("@page")) + s.cssRules.length > 0 && + [...s.cssRules].some(r => r.cssText.includes("@page")) ); }; }, @@ -2007,12 +2004,8 @@ describe("PDF viewer", () => { await closePages(pages); }); - // The print service injects an inline - // to match the PDF's page - // dimensions. If the CSP `style-src-elem` directive blocks inline - // - at print time (web/pdf_print_service.js, web/firefox_print_service.js) - to match the PDF's page dimensions. Since the size varies per PDF the - content can't be pre-hashed, so style-src-elem allows 'unsafe-inline'. - Inline style="…" attributes stay blocked via style-src (no fallback). - --> diff --git a/web/viewer.html b/web/viewer.html index 66d22271a..c7e60ca86 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -29,33 +29,26 @@ See https://github.com/adobe-type-tools/cmap-resources PDF.js viewer -