FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

perf(core): skip CSS work whose result cannot change by triniwiz · Pull Request #11506 · NativeScript/NativeScript · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .ts  (6) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
53 changes: 52 additions & 1 deletion packages/core/ui/core/view-base/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { profile } from '../../../profiling';

import { DOMNode } from '../../../debugger/dom-types';
import { applyInlineStyle, CssState, StyleScope } from '../../styling/style-scope';
import { isClassDependent } from '../../styling/css-selector';
import { booleanConverter } from './utils';

export { booleanConverter } from './utils';
Expand Down Expand Up @@ -225,6 +226,12 @@ export function querySelectorAll(view: ViewBase, selector: string): Array<ViewBa
* @param view - Starting view (parent container).
* @param callback - A function to execute on every child. If function returns false it breaks the iteration.
*/
function restyleDescendant(view: ViewBase): boolean {
view._cssState.onChange();

return true;
}

export function eachDescendant(view: ViewBase, callback: (child: ViewBase) => boolean) {
if (!callback || !view) {
return;
Expand Down Expand Up @@ -1497,6 +1504,39 @@ export abstract class ViewBase extends Observable {
});
}

/**
* Restyles this view and only the descendants whose selectors read one of the changed classes.
*/
_onCssClassesChange(changedCssClasses: string[]): void {
const scope = this._styleScope;
const hadScopedVariables = this.style.hasScopedCssVariables();
this._cssState.onChange();

// Scoped variables flow to every descendant through var().
const dependents = !scope || hadScopedVariables || this.style.hasScopedCssVariables() ? null : scope.classChangeDependents(changedCssClasses);
if (dependents && !dependents.universal && !dependents.types.size && !dependents.ids.size && !dependents.classes.size) {
return;
}

const visit = (view: ViewBase) => {
view.eachChild((child: ViewBase) => {
if (!dependents || child._styleScope !== scope || isClassDependent(child, dependents)) {
const hadVariables = child.style.hasScopedCssVariables();
child._cssState.onChange();
// Variables it defines (or defined) reach every descendant through var().
if (hadVariables || child.style.hasScopedCssVariables()) {
eachDescendant(child, restyleDescendant);
return true;
}
}
visit(child);

return true;
});
};
visit(this);
}

_inheritStyleScope(styleScope: StyleScope): void {
// If we are styleScope don't inherit parent stylescope.
// TODO: Consider adding parent scope and merge selectors.
Expand Down Expand Up @@ -1620,6 +1660,7 @@ export const classNameProperty = new Property<ViewBase, string>({
name: 'className',
valueChanged(view: ViewBase, oldValue: string, newValue: string) {
const cssClasses = view.cssClasses;
const previousCssClasses = new Set(cssClasses);
const rootViewsCssClasses = CSSUtils.getSystemCssClasses();

const shouldAddModalRootViewCssClasses = cssClasses.has(CSSUtils.MODAL_ROOT_VIEW_CSS_CLASS);
Expand Down Expand Up @@ -1651,7 +1692,17 @@ export const classNameProperty = new Property<ViewBase, string>({
}
}

view._onCssStateChange();
const changedCssClasses: string[] = [];
for (const cssClass of cssClasses) {
if (!previousCssClasses.delete(cssClass)) {
changedCssClasses.push(cssClass);
}
}
for (const cssClass of previousCssClasses) {
changedCssClasses.push(cssClass);
}

view._onCssClassesChange(changedCssClasses);
},
});
classNameProperty.register(ViewBase);
Expand Down
134 changes: 131 additions & 3 deletions packages/core/ui/styling/css-selector.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,75 @@ function appendSelectorCandidates(candidates: SelectorCore[], selectors: Selecto
}
}

/**
* The subjects of the selectors a class change can affect, by how they are indexed.
*/
export interface ClassDependents {
universal: boolean;
types: Set<string>;
ids: Set<string>;
classes: Set<string>;
}

export function createClassDependents(): ClassDependents {
return { universal: false, types: new Set(), ids: new Set(), classes: new Set() };
}

function mergeClassDependents(into: ClassDependents, from: ClassDependents): void {
into.universal ||= from.universal;
from.types.forEach((type) => into.types.add(type));
from.ids.forEach((id) => into.ids.add(id));
from.classes.forEach((cssClass) => into.classes.add(cssClass));
}

/** True when a node is the subject of one of the dependent selectors. */
export function isClassDependent(node: Node, dependents: ClassDependents): boolean {
if (dependents.universal || dependents.types.has(node.cssType) || (node.id && dependents.ids.has(node.id))) {
return true;
}
const cssClasses = node.cssClasses;
if (cssClasses && dependents.classes.size) {
for (const cssClass of cssClasses) {
if (dependents.classes.has(cssClass)) {
return true;
}
}
}

return false;
}

/**
* Collects the classes a selector reads from nodes other than its subject (ancestors,
* siblings, functional pseudo-class arguments). Returns true if a class attribute selector does too.
*/
function collectNonSubjectClasses(sel: SelectorBase, classes: Set<string>, isSubject: boolean): boolean {
let readsAttributes = false;
if (sel instanceof ComplexSelector) {
const parts = sel.selectors;
for (let i = 0, length = parts.length; i < length; i++) {
readsAttributes = collectNonSubjectClasses(parts[i], classes, isSubject && i === length - 1) || readsAttributes;
}
} else if (sel instanceof SimpleSelectorSequence) {
for (const part of sel.selectors) {
readsAttributes = collectNonSubjectClasses(part, classes, isSubject) || readsAttributes;
}
} else if (sel instanceof FunctionalPseudoClassSelector) {
// Arguments may hold combinators, so everything inside counts.
for (const part of (sel as unknown as { selectors: SelectorBase[] }).selectors) {
readsAttributes = collectNonSubjectClasses(part, classes, false) || readsAttributes;
}
} else if (sel instanceof ClassSelector) {
if (!isSubject) {
classes.add(sel.cssClass);
}
} else if (sel instanceof AttributeSelector) {
readsAttributes = !isSubject && /^class/i.test(sel.attribute);
}

return readsAttributes;
}

export abstract class SelectorScope<T extends Node> implements LookupSorter {
private id: SelectorMap = {};
private class: SelectorMap = {};
Expand All @@ -1183,6 +1252,31 @@ export abstract class SelectorScope<T extends Node> implements LookupSorter {
* True when any selector in the scope contains a general sibling ('~') combinator.
*/
public hasSiblingCombinatorSelectors = false;
/**
* Classes used outside a selector's subject, mapped to the subjects of the selectors they gate.
*/
public classDependents = new Map<string, ClassDependents>();
/**
* True when a class attribute selector sits outside a subject, so any class change can matter.
*/
public hasAttributeDependents = false;

/**
* Adds the subjects a change to `changedClasses` can restyle; returns false when unknown.
*/
public collectClassDependents(changedClasses: string[], into: ClassDependents): boolean {
if (this.hasAttributeDependents) {
return false;
}
for (let i = 0, length = changedClasses.length; i < length; i++) {
const subjects = this.classDependents.get(changedClasses[i]);
if (subjects) {
mergeClassDependents(into, subjects);
}
}

return true;
}

getSelectorCandidates(node: T, candidates: SelectorCore[] = []): SelectorCore[] {
const { cssClasses, id, cssType } = node;
Expand All @@ -1208,18 +1302,37 @@ export abstract class SelectorScope<T extends Node> implements LookupSorter {
}

sortById(id: string, sel: SelectorCore): void {
this.indexClassDependents(sel, (subjects) => subjects.ids.add(id));
this.addToMap(this.id, id, sel);
}
sortByClass(cssClass: string, sel: SelectorCore): void {
this.indexClassDependents(sel, (subjects) => subjects.classes.add(cssClass));
this.addToMap(this.class, cssClass, sel);
}
sortByType(cssType: string, sel: SelectorCore): void {
this.indexClassDependents(sel, (subjects) => subjects.types.add(cssType));
this.addToMap(this.type, cssType, sel);
}
sortAsUniversal(sel: SelectorCore): void {
this.indexClassDependents(sel, (subjects) => (subjects.universal = true));
this.universal.push(this.makeDocSelector(sel));
}

private indexClassDependents(sel: SelectorCore, addSubject: (subjects: ClassDependents) => void): void {
const classes = new Set<string>();
if (collectNonSubjectClasses(sel, classes, true)) {
this.hasAttributeDependents = true;
}
for (const cssClass of classes) {
let subjects = this.classDependents.get(cssClass);
if (!subjects) {
subjects = createClassDependents();
this.classDependents.set(cssClass, subjects);
}
addSubject(subjects);
}
}

private addToMap(map: SelectorMap, head: string, sel: SelectorCore): void {
if (!map[head]) {
map[head] = [];
Expand Down Expand Up @@ -1269,6 +1382,21 @@ export class StyleSheetSelectorScope<T extends Node> extends SelectorScope<T> {
this.lookupRulesets(rulesets);
}

public collectClassDependents(changedClasses: string[], into: ClassDependents): boolean {
if (!super.collectClassDependents(changedClasses, into)) {
return false;
}
if (this.mediaQuerySelectorScopes) {
for (const selectorScope of this.mediaQuerySelectorScopes) {
if (!selectorScope.collectClassDependents(changedClasses, into)) {
return false;
}
}
}

return true;
}

/**
* Index rulesets added after this scope was built; the rules already indexed
* keep their positions.
Expand Down Expand Up @@ -1365,7 +1493,7 @@ export class StyleSheetSelectorScope<T extends Node> extends SelectorScope<T> {
}

/** Cascade order: specificity, then source order - (tier, position) across stylesheets. */
function compareSelectors(a: SelectorCore, b: SelectorCore): number {
export function compareSelectors(a: SelectorCore, b: SelectorCore): number {
return a.specificity - b.specificity || a.tier - b.tier || a.pos - b.pos;
}

Expand All @@ -1374,7 +1502,7 @@ function compareSelectors(a: SelectorCore, b: SelectorCore): number {
* `scopedTags` filters out rules registered on behalf of a stylesheet this scope
* never loaded; pass it only when such rules exist - it costs a lookup per candidate.
*/
export function matchSelectorCandidates<T extends Node>(node: T, candidates: SelectorCore[], scopedTags?: Set<string>): SelectorsMatch<T> {
export function matchSelectorCandidates<T extends Node>(node: T, candidates: SelectorCore[], scopedTags?: Set<string>, presorted = false): SelectorsMatch<T> {
const selectorsMatch = new SelectorsMatch<T>();

let matched = 0;
Expand All @@ -1394,7 +1522,7 @@ export function matchSelectorCandidates<T extends Node>(node: T, candidates: Sel
}
candidates.length = matched;

selectorsMatch.selectors = candidates.sort(compareSelectors);
selectorsMatch.selectors = presorted ? candidates : candidates.sort(compareSelectors);

return selectorsMatch;
}
Expand Down
26 changes: 20 additions & 6 deletions packages/core/ui/styling/style-properties.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -707,14 +707,28 @@ export const backgroundImageProperty = new CssProperty<Style, string | LinearGra
});
backgroundImageProperty.register(Style);

// Colors are immutable, so every view styled with the same text shares one (and its native color).
const cssColors = new Map<string, Color>();
function cssColor(value: string): Color {
let color = cssColors.get(value);
if (!color) {
if (cssColors.size >= 512) {
cssColors.clear();
}
color = new Color(value);
cssColors.set(value, color);
}
return color;
}

export const backgroundColorProperty = new CssAnimationProperty<Style, Color>({
name: 'backgroundColor',
cssName: 'background-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
valueConverter: cssColor,
});
backgroundColorProperty.register(Style);

Expand Down Expand Up @@ -786,7 +800,7 @@ export const borderTopColorProperty = new CssProperty<Style, Color>({
target.backgroundInternal = target.backgroundInternal.withBorderTopColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
valueConverter: cssColor,
});
borderTopColorProperty.register(Style);

Expand All @@ -797,7 +811,7 @@ export const borderRightColorProperty = new CssProperty<Style, Color>({
target.backgroundInternal = target.backgroundInternal.withBorderRightColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
valueConverter: cssColor,
});
borderRightColorProperty.register(Style);

Expand All @@ -808,7 +822,7 @@ export const borderBottomColorProperty = new CssProperty<Style, Color>({
target.backgroundInternal = target.backgroundInternal.withBorderBottomColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
valueConverter: cssColor,
});
borderBottomColorProperty.register(Style);

Expand All @@ -819,7 +833,7 @@ export const borderLeftColorProperty = new CssProperty<Style, Color>({
target.backgroundInternal = target.backgroundInternal.withBorderLeftColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
valueConverter: cssColor,
});
borderLeftColorProperty.register(Style);

Expand Down Expand Up @@ -1163,7 +1177,7 @@ export const colorProperty = new InheritedCssProperty<Style, Color>({
name: 'color',
cssName: 'color',
equalityComparer: Color.equals,
valueConverter: (v) => new Color(v),
valueConverter: cssColor,
});
colorProperty.register(Style);

Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL