From fbf95c02f627c83efb0f95a35720cfa6d284957b Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Mon, 18 Nov 2019 07:59:40 +1100 Subject: [PATCH 01/26] support native CSSStyleSheet --- src/lib/css-tag.ts | 12 +++-- src/lit-element.ts | 75 ++++++++++++++++++---------- src/test/lit-element_styling_test.ts | 30 +++++++++++ 3 files changed, 86 insertions(+), 31 deletions(-) diff --git a/src/lib/css-tag.ts b/src/lib/css-tag.ts index 471939f5..a921884e 100644 --- a/src/lib/css-tag.ts +++ b/src/lib/css-tag.ts @@ -12,8 +12,9 @@ found at http://polymer.github.io/PATENTS.txt /** * Whether the current browser supports `adoptedStyleSheets`. */ -export const supportsAdoptingStyleSheets = - ('adoptedStyleSheets' in Document.prototype) && +export const supportsAdoptingShadowStyleSheets = + (window.ShadowRoot) && + ('adoptedStyleSheets' in ShadowRoot.prototype) && ('replace' in CSSStyleSheet.prototype); const constructionToken = Symbol(); @@ -28,6 +29,7 @@ export class CSSResult { throw new Error( 'CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'); } + this.cssText = cssText; } @@ -35,9 +37,9 @@ export class CSSResult { // stylesheets are not created until the first element instance is made. get styleSheet(): CSSStyleSheet|null { if (this._styleSheet === undefined) { - // Note, if `adoptedStyleSheets` is supported then we assume CSSStyleSheet - // is constructable. - if (supportsAdoptingStyleSheets) { + // Note, if `supportsAdoptingShadowStyleSheets` is supported then we + // assume CSSStyleSheet is constructable. + if (supportsAdoptingShadowStyleSheets) { this._styleSheet = new CSSStyleSheet(); this._styleSheet.replaceSync(this.cssText); } else { diff --git a/src/lit-element.ts b/src/lit-element.ts index 41817d94..ed3dc266 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -18,7 +18,7 @@ import {PropertyValues, UpdatingElement} from './lib/updating-element.js'; export * from './lib/updating-element.js'; export * from './lib/decorators.js'; export {html, svg, TemplateResult, SVGTemplateResult} from 'lit-html/lit-html.js'; -import {supportsAdoptingStyleSheets, CSSResult} from './lib/css-tag.js'; +import {supportsAdoptingShadowStyleSheets, CSSResult} from './lib/css-tag.js'; export * from './lib/css-tag.js'; declare global { @@ -33,7 +33,9 @@ declare global { (window['litElementVersions'] || (window['litElementVersions'] = [])) .push('2.3.1'); -export interface CSSResultArray extends Array {} +export type CSSResultOrNative = CSSResult|CSSStyleSheet; + +export interface CSSResultArray extends Array {} /** * Sentinal value used to avoid calling lit-html's render function when @@ -82,9 +84,10 @@ export class LitElement extends UpdatingElement { * Array of styles to apply to the element. The styles should be defined * using the [[`css`]] tag function. */ - static styles?: CSSResult|CSSResultArray; + static styles?: CSSResultOrNative|CSSResultArray; private static _styles: CSSResult[]|undefined; + private static _nativeStyles: CSSStyleSheet[]|undefined; /** * Return the array of styles to apply to the element. @@ -92,26 +95,25 @@ export class LitElement extends UpdatingElement { * * @nocollapse */ - static getStyles(): CSSResult|CSSResultArray|undefined { + static getStyles(): CSSResultOrNative|CSSResultArray|undefined { return this.styles; } /** @nocollapse */ private static _getUniqueStyles() { // Only gather styles once per class - if (this.hasOwnProperty(JSCompiler_renameProperty('_styles', this))) { + const property = supportsAdoptingShadowStyleSheets ? + JSCompiler_renameProperty('_nativeStyles', this) : + JSCompiler_renameProperty('_styles', this); + if (this.hasOwnProperty(property)) { return; } // Take care not to call `this.getStyles()` multiple times since this // generates new CSSResults each time. - // TODO(sorvell): Since we do not cache CSSResults by input, any - // shared styles will generate new stylesheet objects, which is wasteful. - // This should be addressed when a browser ships constructable - // stylesheets. - const userStyles = this.getStyles(); - if (userStyles === undefined) { - this._styles = []; - } else if (Array.isArray(userStyles)) { + const userStyles = this.styles!; + const work = []; + + if (Array.isArray(userStyles)) { // De-duplicate styles preserving the _last_ instance in the set. // This is a performance optimization to avoid duplicated styles that can // occur especially when composing via subclassing. @@ -119,20 +121,38 @@ export class LitElement extends UpdatingElement { // assumption that it's most important that last added styles override // previous styles. const addStyles = - (styles: CSSResultArray, set: Set): Set => + (styles: CSSResultArray, set: Set): Set => styles.reduceRight( - (set: Set, s) => + (set: Set, s) => // Note: On IE set.add() does not return the set Array.isArray(s) ? addStyles(s, set) : (set.add(s), set), set); // Array.from does not work on Set in IE, otherwise return // Array.from(addStyles(userStyles, new Set())).reverse() - const set = addStyles(userStyles, new Set()); - const styles: CSSResult[] = []; - set.forEach((v) => styles.unshift(v)); - this._styles = styles; + const set = addStyles(userStyles, new Set()); + set.forEach((v) => work.unshift(v)); + } else if (userStyles !== undefined) { + work.push(userStyles); + } + + if (supportsAdoptingShadowStyleSheets) { + // Convert all CSSResult instances to native CSSStyleSheet. + this._nativeStyles = work.map((resultOrNative) => { + if (resultOrNative instanceof CSSResult) { + return resultOrNative.styleSheet!; + } + return resultOrNative; + }); } else { - this._styles = [userStyles]; + // This is only possible when a user passes a CSSStyleSheet that was + // read from a