From 9ccc54b6035c04e383d9256fb3ad9c0c7b00fa20 Mon Sep 17 00:00:00 2001 From: farfromrefuge Date: Thu, 8 Sep 2022 01:47:38 +0200 Subject: [PATCH] perf: faster style-scope setPropertyValues (#9083) --- .../core/ui/styling/css-selector/index.ts | 2 +- packages/core/ui/styling/style-scope.ts | 95 ++++++++++--------- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/packages/core/ui/styling/css-selector/index.ts b/packages/core/ui/styling/css-selector/index.ts index 4f8b6556d..704fcf266 100644 --- a/packages/core/ui/styling/css-selector/index.ts +++ b/packages/core/ui/styling/css-selector/index.ts @@ -671,7 +671,7 @@ interface ChangeAccumulator { export class SelectorsMatch implements ChangeAccumulator { public changeMap: ChangeMap = new Map(); - public selectors; + public selectors: SelectorCore[]; public addAttribute(node: T, attribute: string): void { const deps: Changes = this.properties(node); diff --git a/packages/core/ui/styling/style-scope.ts b/packages/core/ui/styling/style-scope.ts index cb0bffdd9..4ccb2228b 100644 --- a/packages/core/ui/styling/style-scope.ts +++ b/packages/core/ui/styling/style-scope.ts @@ -74,7 +74,7 @@ export function mergeCssSelectors(): void { let applicationCssSelectors: RuleSet[] = []; let applicationCssSelectorVersion = 0; let applicationSelectors: RuleSet[] = []; -let tagToScopeTag: Map = new Map(); +const tagToScopeTag: Map = new Map(); let currentScopeTag: string = null; const applicationAdditionalSelectors: RuleSet[] = []; const applicationKeyframes: any = {}; @@ -314,7 +314,7 @@ export function removeTaggedAdditionalCSS(tag: string | number): boolean { export function addTaggedAdditionalCSS(cssText: string, tag?: string | number): boolean { const parsed: RuleSet[] = CSSSource.fromDetect(cssText, applicationKeyframes, undefined).selectors; - let tagScope = currentScopeTag || (tag && tagToScopeTag.has(tag) && tagToScopeTag.get(tag)) || null; + const tagScope = currentScopeTag || (tag && tagToScopeTag.has(tag) && tagToScopeTag.get(tag)) || null; if (tagScope && tag) { tagToScopeTag.set(tag, tagScope); } @@ -396,7 +396,7 @@ if (application.hasLaunched()) { export class CssState { static emptyChangeMap: Readonly> = Object.freeze(new Map()); - static emptyPropertyBag: Readonly> = Object.freeze({}); + static emptyPropertyBag: Record = {}; static emptyAnimationArray: ReadonlyArray = Object.freeze([]); static emptyMatch: Readonly> = { selectors: [], @@ -408,7 +408,7 @@ export class CssState { _onDynamicStateChangeHandler: () => void; _appliedChangeMap: Readonly>; - _appliedPropertyValues: Readonly>; + private _appliedPropertyValues: Record = CssState.emptyPropertyBag; _appliedAnimations: ReadonlyArray; _appliedSelectorsVersion: number; @@ -570,59 +570,63 @@ export class CssState { matchingSelectors.forEach((selector) => selector.ruleset.declarations.forEach((declaration) => (newPropertyValues[declaration.property] = declaration.value))); const oldProperties = this._appliedPropertyValues; - - let isCssExpressionInUse = false; - // Update values for the scope's css-variables view.style.resetScopedCssVariables(); + const valuesToApply = {}; + const cssExpsProperties = {}; + for (const property in newPropertyValues) { const value = newPropertyValues[property]; - if (isCssVariable(property)) { - view.style.setScopedCssVariable(property, value); + const isCssExp = isCssVariableExpression(value) || isCssCalcExpression(value); - delete newPropertyValues[property]; + if (isCssExp) { + // we handle css exp separately because css vars must be evaluated first + cssExpsProperties[property] = value; continue; } - - isCssExpressionInUse = isCssExpressionInUse || isCssVariableExpression(value) || isCssCalcExpression(value); - } - - if (isCssExpressionInUse) { - // Evalute css-expressions to get the latest values. - for (const property in newPropertyValues) { - const value = evaluateCssExpressions(view, property, newPropertyValues[property]); - if (value === unsetValue) { - delete newPropertyValues[property]; - continue; - } - - newPropertyValues[property] = value; - } - } - - // Property values are fully updated, freeze the object to be used for next update. - Object.freeze(newPropertyValues); - - // Unset removed values - for (const property in oldProperties) { - if (!(property in newPropertyValues)) { - if (property in view.style) { - view.style[`css:${property}`] = unsetValue; - } else { - // TRICKY: How do we unset local value? - } - } - } - - // Set new values to the style - for (const property in newPropertyValues) { - if (oldProperties && property in oldProperties && oldProperties[property] === newPropertyValues[property]) { + delete oldProperties[property]; + if (property in oldProperties && oldProperties[property] === value) { // Skip unchanged values continue; } + if (isCssVariable(property)) { + view.style.setScopedCssVariable(property, value); + delete newPropertyValues[property]; + continue; + } + valuesToApply[property] = value; + } + //we need to parse CSS vars first before evaluating css expressions + for (const property in cssExpsProperties) { + delete oldProperties[property]; + const value = evaluateCssExpressions(view, property, cssExpsProperties[property]); + if (property in oldProperties && oldProperties[property] === value) { + // Skip unchanged values + continue; + } + if (value === unsetValue) { + delete newPropertyValues[property]; + } + if (isCssVariable(property)) { + view.style.setScopedCssVariable(property, value); + delete newPropertyValues[property]; + } - const value = newPropertyValues[property]; + valuesToApply[property] = value; + } + + // Unset removed values + for (const property in oldProperties) { + if (property in view.style) { + view.style[`css:${property}`] = unsetValue; + } else { + // TRICKY: How do we unset local value? + } + } + // Set new values to the style + for (const property in valuesToApply) { + const value = valuesToApply[property]; try { if (property in view.style) { view.style[`css:${property}`] = value; @@ -693,7 +697,6 @@ export class CssState { } } CssState.prototype._appliedChangeMap = CssState.emptyChangeMap; -CssState.prototype._appliedPropertyValues = CssState.emptyPropertyBag; CssState.prototype._appliedAnimations = CssState.emptyAnimationArray; CssState.prototype._matchInvalid = true;