Skip to content

Commit 4e03571

Browse files
authored
Reimplement sharing system to address Safari issues (#88)
* refactor: use alternative implementation approach to fix safari issues * docs: fix README badges * chore: use 2kB for size-limit * test: small fixes
1 parent 6ece916 commit 4e03571

File tree

7 files changed

+77
-90
lines changed

7 files changed

+77
-90
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = [
22
{
33
path: 'dist/adoptedStyleSheets.js',
4-
limit: '2.5 kB',
4+
limit: '2 kB',
55
},
66
];

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Constructible style sheets polyfill
22

3-
[![CI](https://github.com/calebdwilliams/construct-style-sheets/actions/workflows/ci/badge.svg)](https://github.com/calebdwilliams/construct-style-sheets/actions)
3+
[![CI](https://github.com/calebdwilliams/construct-style-sheets/actions/workflows/ci.yml/badge.svg)](https://github.com/calebdwilliams/construct-style-sheets/actions)
44
[![npm version](https://img.shields.io/npm/v/construct-style-sheets-polyfill.svg?style=flat)](https://npmjs.org/package/construct-style-sheets-polyfill 'View this project on npm')
5-
[![codecov](https://codecov.io/gh/calebdwilliams/construct-style-sheets/branch/master/graph/badge.svg)](https://codecov.io/gh/calebdwilliams/construct-style-sheets)
5+
[![codecov](https://codecov.io/gh/calebdwilliams/construct-style-sheets/branch/main/graph/badge.svg)](https://codecov.io/gh/calebdwilliams/construct-style-sheets)
66

77
This package is a polyfill for the [constructible style sheets/adopted style sheets specification](https://github.com/WICG/construct-stylesheets/blob/gh-pages/explainer.md). The full specificaiton is enabled by default in Google Chrome as of version 73.
88

karma.conf.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,14 @@ module.exports = (config) => {
163163
isolatedModules: true,
164164
tsconfig: require.resolve('./tsconfig.test.json'),
165165
}),
166-
rollupPluginInstrumentTsCode(),
166+
coverage && rollupPluginInstrumentTsCode(),
167167
rollupPluginInjectCode({
168168
'index.js': {
169169
line: 3,
170170
code: " if ('adoptedStyleSheets' in document) { return; }\n",
171171
},
172172
}),
173-
],
173+
].filter(Boolean),
174174
output: {
175175
format: 'iife',
176176
name: 'source',

src/ConstructedStyleSheet.ts

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type Location from './Location';
2-
import {fixBrokenRules, hasBrokenRules} from './safari';
32
import {_DOMException, bootstrapper, defineProperty} from './shared';
4-
import {clearRules, insertAllRules, rejectImports} from './utils';
3+
import {rejectImports} from './utils';
54

65
const cssStyleSheetMethods = [
76
'addRule',
@@ -45,12 +44,15 @@ export function isNonConstructedStyleSheetInstance(instance: object): boolean {
4544
*/
4645

4746
/**
48-
* Basic stylesheet is an sample stylesheet that contains all the CSS rules of
49-
* the current constructable stylesheet. The document or custom elements can
50-
* adopt constructable stylesheet; in this case, basic stylesheet's CSS rules
51-
* are reflected in document/custom element internal <style> elements.
47+
* Basic style element is a sample element that contains all the CSS of the
48+
* current constructable stylesheet. The document or custom elements can
49+
* adopt constructable stylesheet; in this case, basic stylesheet's CSS is
50+
* reflected in document/custom element internal <style> elements.
5251
*/
53-
const $basicStyleSheet = new WeakMap<ConstructedStyleSheet, CSSStyleSheet>();
52+
const $basicStyleElement = new WeakMap<
53+
ConstructedStyleSheet,
54+
HTMLStyleElement
55+
>();
5456

5557
/**
5658
* Contains all locations associated with the current ConstructedStyleSheet.
@@ -68,6 +70,20 @@ const $adoptersByLocation = new WeakMap<
6870
WeakMap<Location, HTMLStyleElement>
6971
>();
7072

73+
type CSSStyleSheetMethodMeta = Readonly<{
74+
args: IArguments;
75+
method: string;
76+
}>;
77+
78+
/**
79+
* Contains all the methods called for the constructable style sheet. Used to
80+
* reflect these methods in created or restyled `<style>` adopters.
81+
*/
82+
const $appliedMethods = new WeakMap<
83+
ConstructedStyleSheet,
84+
Array<CSSStyleSheetMethodMeta>
85+
>();
86+
7187
/*
7288
* Package-level control functions
7389
*/
@@ -111,8 +127,12 @@ export function restyleAdopter(
111127
adopter: HTMLStyleElement,
112128
): void {
113129
requestAnimationFrame(() => {
114-
clearRules(adopter.sheet!);
115-
insertAllRules($basicStyleSheet.get(sheet)!, adopter.sheet!);
130+
adopter.textContent = $basicStyleElement.get(sheet)!.textContent;
131+
$appliedMethods
132+
.get(sheet)!
133+
.forEach((command) =>
134+
adopter.sheet![command.method].apply(adopter.sheet!, command.args),
135+
);
116136
});
117137
}
118138

@@ -126,7 +146,7 @@ export function restyleAdopter(
126146
* properties are initialized.
127147
*/
128148
function checkInvocationCorrectness(self: ConstructedStyleSheet) {
129-
if (!$basicStyleSheet.has(self)) {
149+
if (!$basicStyleElement.has(self)) {
130150
throw new TypeError('Illegal invocation');
131151
}
132152
}
@@ -149,9 +169,10 @@ function ConstructedStyleSheet(this: ConstructedStyleSheet) {
149169
bootstrapper.body.appendChild(style);
150170

151171
// Init private properties
152-
$basicStyleSheet.set(self, style.sheet!);
172+
$basicStyleElement.set(self, style);
153173
$locations.set(self, []);
154174
$adoptersByLocation.set(self, new WeakMap());
175+
$appliedMethods.set(self, []);
155176
}
156177

157178
const proto = ConstructedStyleSheet.prototype;
@@ -175,11 +196,8 @@ proto.replaceSync = function replaceSync(contents) {
175196
if (typeof contents === 'string') {
176197
const self = this;
177198

178-
const style = $basicStyleSheet.get(self)!.ownerNode as HTMLStyleElement;
179-
style.textContent = hasBrokenRules
180-
? fixBrokenRules(rejectImports(contents))
181-
: rejectImports(contents);
182-
$basicStyleSheet.set(self, style.sheet!);
199+
$basicStyleElement.get(self)!.textContent = rejectImports(contents);
200+
$appliedMethods.set(self, []);
183201

184202
$locations.get(self)!.forEach((location) => {
185203
if (location.isConnected()) {
@@ -197,7 +215,7 @@ defineProperty(proto, 'cssRules', {
197215
// CSSStyleSheet.prototype.cssRules;
198216
checkInvocationCorrectness(this);
199217

200-
return $basicStyleSheet.get(this)!.cssRules;
218+
return $basicStyleElement.get(this)!.sheet!.cssRules;
201219
},
202220
});
203221

@@ -208,6 +226,8 @@ cssStyleSheetMethods.forEach((method) => {
208226

209227
const args = arguments;
210228

229+
$appliedMethods.get(self)!.push({method, args});
230+
211231
$locations.get(self)!.forEach((location) => {
212232
if (location.isConnected()) {
213233
// Type Note: If location is connected, adopter is already created; and
@@ -217,19 +237,9 @@ cssStyleSheetMethods.forEach((method) => {
217237
}
218238
});
219239

220-
if (hasBrokenRules) {
221-
if (method === 'insertRule') {
222-
args[0] = fixBrokenRules(args[0]);
223-
}
224-
225-
if (method === 'addRule') {
226-
args[1] = fixBrokenRules(args[1]);
227-
}
228-
}
229-
230-
const basic = $basicStyleSheet.get(self)!;
240+
const basicSheet = $basicStyleElement.get(self)!.sheet!;
231241

232-
return basic[method].apply(basic, args);
242+
return basicSheet[method].apply(basicSheet, args);
233243
};
234244
});
235245

src/safari.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/utils.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import {getCssText} from './safari';
2-
import {closedShadowRootRegistry, forEach} from './shared';
1+
import {closedShadowRootRegistry} from './shared';
32

43
const importPattern = /@import.+?;?$/gm;
54

@@ -15,18 +14,6 @@ export function rejectImports(contents: string): string {
1514
return _contents.trim();
1615
}
1716

18-
export function clearRules(sheet: CSSStyleSheet): void {
19-
for (let i = 0; i < sheet.cssRules.length; i++) {
20-
sheet.deleteRule(0);
21-
}
22-
}
23-
24-
export function insertAllRules(from: CSSStyleSheet, to: CSSStyleSheet): void {
25-
forEach.call(from.cssRules, (rule, i) => {
26-
to.insertRule(getCssText(rule), i);
27-
});
28-
}
29-
3017
/**
3118
* Cross-platform check for the element to be connected to the DOM
3219
*/

test/polyfill.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,5 +718,37 @@ describe('Constructible Style Sheets polyfill', () => {
718718
checkContent();
719719
});
720720
});
721+
722+
describe('"background" rule', () => {
723+
let css: CSSStyleSheet;
724+
let element: HTMLElement;
725+
726+
const checkContent = () => checkGlobalCss(element, {background: ''});
727+
728+
beforeEach(async () => {
729+
css = new CSSStyleSheet();
730+
document.adoptedStyleSheets = [css];
731+
element = await fixture('<div class="foo"></div>');
732+
});
733+
734+
it('handles rule on replace', () => {
735+
css.replaceSync(
736+
'.foo { background: none !important; } .foo { background: blue; }',
737+
);
738+
checkContent();
739+
});
740+
741+
it('handles rule on insertRule', () => {
742+
css.insertRule('.foo { background: none !important; }', 0);
743+
css.insertRule('.foo { background: blue; }', 1);
744+
checkContent();
745+
});
746+
747+
it('handles rule on addRule', () => {
748+
css.addRule('.foo', 'background: none !important', 0);
749+
css.addRule('.foo', 'background: blue', 1);
750+
checkContent();
751+
});
752+
});
721753
});
722754
});

0 commit comments

Comments
 (0)