Skip to content

Commit 6a6871e

Browse files
fix: CSS back to <style> for Safari/cascade reasons (#9908)
Reverts the storage mechanism introduced in #9611 (constructable stylesheets via `adoptedStyleSheets`) while keeping the per-root injection-site tracking that #9611 added for shadow-DOM support. Motivations: - Safari 15.4 compatibility. `new CSSStyleSheet()` and `adoptedStyleSheets` require Safari 16.4+ - Cascade order. `adoptedStyleSheets` apply after `<style>`/`<link>` elements in the document, so Blockly's defaults silently overrode host stylesheets. Prepending a `<style>` to the head (or to the shadow root) restores the pre-#9611 behavior where any author stylesheet declared later wins on specificity ties. Trade-offs: - Per-shadow-root CSS text is duplicated rather than shared via a single adopted sheet object. Negligible for typical use. - `Css.register()` calls made after the first `inject()` no longer reach already-injected roots (same as #9611's behavior); subsequent `inject()` calls into other roots still pick them up. Web-component consumers can legitimately register late, so this is preferred to reinstating the pre-#9611 throw. Fixes #9876
1 parent 8bf2e1e commit 6a6871e

2 files changed

Lines changed: 24 additions & 29 deletions

File tree

packages/blockly/core/css.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
*/
66

77
// Former goog.module ID: Blockly.Css
8-
/** Has CSS already been injected? */
98
const injectionSites = new WeakSet<Document | ShadowRoot>();
10-
const registeredStyleSheets: Array<CSSStyleSheet> = [];
9+
const registeredCss: Array<string> = [];
1110
import * as userAgent from './utils/useragent.js';
1211

1312
/**
@@ -17,11 +16,7 @@ import * as userAgent from './utils/useragent.js';
1716
* @param cssContent Multiline CSS string or an array of single lines of CSS.
1817
*/
1918
export function register(cssContent: string) {
20-
if (typeof window === 'undefined' || !window.CSSStyleSheet) return;
21-
22-
const sheet = new CSSStyleSheet();
23-
sheet.replace(cssContent);
24-
registeredStyleSheets.push(sheet);
19+
registeredCss.push(cssContent);
2520
}
2621

2722
/**
@@ -41,24 +36,26 @@ export function inject(
4136
hasCss: boolean,
4237
pathToMedia: string,
4338
) {
44-
if (!hasCss || typeof window === 'undefined' || !window.CSSStyleSheet) {
45-
return;
46-
}
39+
if (!hasCss || typeof window === 'undefined') return;
4740

4841
const root = container.getRootNode() as Document | ShadowRoot;
49-
// Only inject the CSS once.
5042
if (injectionSites.has(root)) return;
5143
injectionSites.add(root);
5244

5345
// Strip off any trailing slash (either Unix or Windows).
5446
const mediaPath = pathToMedia.replace(/[\\/]$/, '');
55-
const cssContent = content.replace(/<<<PATH>>>/g, mediaPath);
56-
57-
const sheet = new CSSStyleSheet();
58-
sheet.replace(cssContent);
59-
root.adoptedStyleSheets.push(sheet);
60-
61-
registeredStyleSheets.forEach((sheet) => root.adoptedStyleSheets.push(sheet));
47+
const cssText = [content, ...registeredCss]
48+
.join('\n')
49+
.replace(/<<<PATH>>>/g, mediaPath);
50+
51+
const styleEl = document.createElement('style');
52+
styleEl.id = 'blockly-common-style';
53+
styleEl.textContent = cssText;
54+
// Prepend so Blockly's rules sit at the start of the cascade; any user
55+
// stylesheet declared later wins by document order. Style elements appended
56+
// to the light DOM don't apply inside shadow roots, so for the shadow DOM
57+
// case we prepend the style element to the shadow root itself.
58+
(root instanceof ShadowRoot ? root : document.head).prepend(styleEl);
6259
}
6360

6461
/**

packages/blockly/core/renderers/common/constants.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,17 +1126,15 @@ export class ConstantProvider {
11261126
* @param selector The CSS selector to interpolate into the stylesheet.
11271127
*/
11281128
protected injectCSS_(root: Document | ShadowRoot, selector: string) {
1129-
if (
1130-
typeof window === 'undefined' ||
1131-
!window.CSSStyleSheet ||
1132-
injectionSites.get(selector)?.has(root)
1133-
) {
1134-
return;
1135-
}
1136-
1137-
const sheet = new CSSStyleSheet();
1138-
sheet.replace(this.getCSS_(selector).join('\n'));
1139-
root.adoptedStyleSheets.push(sheet);
1129+
if (typeof window === 'undefined') return;
1130+
if (injectionSites.get(selector)?.has(root)) return;
1131+
1132+
const styleEl = document.createElement('style');
1133+
styleEl.className = 'blockly-renderer-style';
1134+
styleEl.textContent = this.getCSS_(selector).join('\n');
1135+
// See css.ts inject() for the rationale on prepending and shadow root
1136+
// handling.
1137+
(root instanceof ShadowRoot ? root : document.head).prepend(styleEl);
11401138

11411139
const sitesForSelector =
11421140
injectionSites.get(selector) ?? new WeakSet<Document | ShadowRoot>();

0 commit comments

Comments
 (0)