Skip to content

Commit

Permalink
Reimplement sharing system to address Safari issues (#88)
Browse files Browse the repository at this point in the history
* refactor: use alternative implementation approach to fix safari issues

* docs: fix README badges

* chore: use 2kB for size-limit

* test: small fixes
Lodin authored Oct 13, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 6ece916 commit 4e03571
Showing 7 changed files with 77 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = [
{
path: 'dist/adoptedStyleSheets.js',
limit: '2.5 kB',
limit: '2 kB',
},
];
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Constructible style sheets polyfill

[![CI](https://github.com/calebdwilliams/construct-style-sheets/actions/workflows/ci/badge.svg)](https://github.com/calebdwilliams/construct-style-sheets/actions)
[![CI](https://github.com/calebdwilliams/construct-style-sheets/actions/workflows/ci.yml/badge.svg)](https://github.com/calebdwilliams/construct-style-sheets/actions)
[![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')
[![codecov](https://codecov.io/gh/calebdwilliams/construct-style-sheets/branch/master/graph/badge.svg)](https://codecov.io/gh/calebdwilliams/construct-style-sheets)
[![codecov](https://codecov.io/gh/calebdwilliams/construct-style-sheets/branch/main/graph/badge.svg)](https://codecov.io/gh/calebdwilliams/construct-style-sheets)

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.

4 changes: 2 additions & 2 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -163,14 +163,14 @@ module.exports = (config) => {
isolatedModules: true,
tsconfig: require.resolve('./tsconfig.test.json'),
}),
rollupPluginInstrumentTsCode(),
coverage && rollupPluginInstrumentTsCode(),
rollupPluginInjectCode({
'index.js': {
line: 3,
code: " if ('adoptedStyleSheets' in document) { return; }\n",
},
}),
],
].filter(Boolean),
output: {
format: 'iife',
name: 'source',
68 changes: 39 additions & 29 deletions src/ConstructedStyleSheet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type Location from './Location';
import {fixBrokenRules, hasBrokenRules} from './safari';
import {_DOMException, bootstrapper, defineProperty} from './shared';
import {clearRules, insertAllRules, rejectImports} from './utils';
import {rejectImports} from './utils';

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

/**
* Basic stylesheet is an sample stylesheet that contains all the CSS rules of
* the current constructable stylesheet. The document or custom elements can
* adopt constructable stylesheet; in this case, basic stylesheet's CSS rules
* are reflected in document/custom element internal <style> elements.
* Basic style element is a sample element that contains all the CSS of the
* current constructable stylesheet. The document or custom elements can
* adopt constructable stylesheet; in this case, basic stylesheet's CSS is
* reflected in document/custom element internal <style> elements.
*/
const $basicStyleSheet = new WeakMap<ConstructedStyleSheet, CSSStyleSheet>();
const $basicStyleElement = new WeakMap<
ConstructedStyleSheet,
HTMLStyleElement
>();

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

type CSSStyleSheetMethodMeta = Readonly<{
args: IArguments;
method: string;
}>;

/**
* Contains all the methods called for the constructable style sheet. Used to
* reflect these methods in created or restyled `<style>` adopters.
*/
const $appliedMethods = new WeakMap<
ConstructedStyleSheet,
Array<CSSStyleSheetMethodMeta>
>();

/*
* Package-level control functions
*/
@@ -111,8 +127,12 @@ export function restyleAdopter(
adopter: HTMLStyleElement,
): void {
requestAnimationFrame(() => {
clearRules(adopter.sheet!);
insertAllRules($basicStyleSheet.get(sheet)!, adopter.sheet!);
adopter.textContent = $basicStyleElement.get(sheet)!.textContent;
$appliedMethods
.get(sheet)!
.forEach((command) =>
adopter.sheet![command.method].apply(adopter.sheet!, command.args),
);
});
}

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

// Init private properties
$basicStyleSheet.set(self, style.sheet!);
$basicStyleElement.set(self, style);
$locations.set(self, []);
$adoptersByLocation.set(self, new WeakMap());
$appliedMethods.set(self, []);
}

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

const style = $basicStyleSheet.get(self)!.ownerNode as HTMLStyleElement;
style.textContent = hasBrokenRules
? fixBrokenRules(rejectImports(contents))
: rejectImports(contents);
$basicStyleSheet.set(self, style.sheet!);
$basicStyleElement.get(self)!.textContent = rejectImports(contents);
$appliedMethods.set(self, []);

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

return $basicStyleSheet.get(this)!.cssRules;
return $basicStyleElement.get(this)!.sheet!.cssRules;
},
});

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

const args = arguments;

$appliedMethods.get(self)!.push({method, args});

$locations.get(self)!.forEach((location) => {
if (location.isConnected()) {
// Type Note: If location is connected, adopter is already created; and
@@ -217,19 +237,9 @@ cssStyleSheetMethods.forEach((method) => {
}
});

if (hasBrokenRules) {
if (method === 'insertRule') {
args[0] = fixBrokenRules(args[0]);
}

if (method === 'addRule') {
args[1] = fixBrokenRules(args[1]);
}
}

const basic = $basicStyleSheet.get(self)!;
const basicSheet = $basicStyleElement.get(self)!.sheet!;

return basic[method].apply(basic, args);
return basicSheet[method].apply(basicSheet, args);
};
});

42 changes: 0 additions & 42 deletions src/safari.ts

This file was deleted.

15 changes: 1 addition & 14 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {getCssText} from './safari';
import {closedShadowRootRegistry, forEach} from './shared';
import {closedShadowRootRegistry} from './shared';

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

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

export function clearRules(sheet: CSSStyleSheet): void {
for (let i = 0; i < sheet.cssRules.length; i++) {
sheet.deleteRule(0);
}
}

export function insertAllRules(from: CSSStyleSheet, to: CSSStyleSheet): void {
forEach.call(from.cssRules, (rule, i) => {
to.insertRule(getCssText(rule), i);
});
}

/**
* Cross-platform check for the element to be connected to the DOM
*/
32 changes: 32 additions & 0 deletions test/polyfill.test.ts
Original file line number Diff line number Diff line change
@@ -718,5 +718,37 @@ describe('Constructible Style Sheets polyfill', () => {
checkContent();
});
});

describe('"background" rule', () => {
let css: CSSStyleSheet;
let element: HTMLElement;

const checkContent = () => checkGlobalCss(element, {background: ''});

beforeEach(async () => {
css = new CSSStyleSheet();
document.adoptedStyleSheets = [css];
element = await fixture('<div class="foo"></div>');
});

it('handles rule on replace', () => {
css.replaceSync(
'.foo { background: none !important; } .foo { background: blue; }',
);
checkContent();
});

it('handles rule on insertRule', () => {
css.insertRule('.foo { background: none !important; }', 0);
css.insertRule('.foo { background: blue; }', 1);
checkContent();
});

it('handles rule on addRule', () => {
css.addRule('.foo', 'background: none !important', 0);
css.addRule('.foo', 'background: blue', 1);
checkContent();
});
});
});
});

0 comments on commit 4e03571

Please sign in to comment.