Skip to content

Commit bd570b9

Browse files
luis100claude
andcommitted
fix: scope pdf_viewer.css to .rodaPdfViewer to prevent global CSS leakage
pdf_viewer.css sets :root CSS custom properties (including dark-mode media queries like --csstools-color-scheme--light) that leaked into the host application and affected unrelated components. Instead of loading the file via a global <link>, we now fetch the CSS text, replace every ':root' selector with '.rodaPdfViewer', and inject the result as a <style> element. Since all PDF.js DOM elements are descendants of .rodaPdfViewer, custom properties defined there are still inherited by .pdfViewer, .page, .textLayer, and .highlight — but they no longer pollute the rest of the page. Falls back to a global <link> if fetch fails (e.g. strict CSP). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent df7d25c commit bd570b9

1 file changed

Lines changed: 41 additions & 7 deletions

File tree

roda-ui/roda-wui/src/main/resources/static/js/rodaPdfViewer.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,56 @@
2222
var _pdfjsViewer = null; /* web/pdf_viewer.mjs */
2323
var _cssLoaded = false;
2424

25+
/* ------------------------------------------------------------------ */
26+
/* Scoped CSS loading */
27+
/* ------------------------------------------------------------------ */
28+
29+
/*
30+
* pdf_viewer.css contains global :root rules (CSS custom properties,
31+
* color-scheme, dark-mode media queries) that leak into the host page.
32+
* We fetch the CSS text, replace every `:root` selector with
33+
* `.rodaPdfViewer` so all declarations are scoped to our container,
34+
* then inject the result as a <style> element.
35+
*
36+
* All PDF.js elements are descendants of .rodaPdfViewer, so custom
37+
* properties defined there are still inherited by .pdfViewer, .page,
38+
* .textLayer, etc.
39+
*/
40+
async function loadScopedViewerCss(baseUrl) {
41+
try {
42+
var response = await fetch(baseUrl + 'webjars/pdfjs-dist/web/pdf_viewer.css');
43+
if (!response.ok) throw new Error('HTTP ' + response.status);
44+
var css = await response.text();
45+
46+
/* Scope :root declarations to .rodaPdfViewer */
47+
css = css.replace(/:root\b/g, '.rodaPdfViewer');
48+
49+
var style = document.createElement('style');
50+
style.setAttribute('data-roda-pdfjs-css', '');
51+
style.textContent = css;
52+
document.head.appendChild(style);
53+
} catch (e) {
54+
console.warn('[RodaPdfViewer] Could not scope pdf_viewer.css; falling back to global link.', e);
55+
var link = document.createElement('link');
56+
link.rel = 'stylesheet';
57+
link.href = baseUrl + 'webjars/pdfjs-dist/web/pdf_viewer.css';
58+
link.setAttribute('data-roda-pdfjs-css', '');
59+
document.head.appendChild(link);
60+
}
61+
}
62+
2563
/* ------------------------------------------------------------------ */
2664
/* Library loading */
2765
/* ------------------------------------------------------------------ */
2866

2967
async function getLibs(baseUrl) {
3068
if (_pdfjs && _pdfjsViewer) return { pdfjs: _pdfjs, pdfjsViewer: _pdfjsViewer };
3169

32-
/* Load pdf_viewer.css once — needed for text-layer CSS custom props */
70+
/* Load pdf_viewer.css once, scoped so it does not leak into the app */
3371
if (!_cssLoaded) {
3472
_cssLoaded = true;
35-
if (!document.querySelector('link[data-roda-pdfjs-css]')) {
36-
var link = document.createElement('link');
37-
link.rel = 'stylesheet';
38-
link.href = baseUrl + 'webjars/pdfjs-dist/web/pdf_viewer.css';
39-
link.setAttribute('data-roda-pdfjs-css', '');
40-
document.head.appendChild(link);
73+
if (!document.querySelector('style[data-roda-pdfjs-css]')) {
74+
await loadScopedViewerCss(baseUrl);
4175
}
4276
}
4377

0 commit comments

Comments
 (0)