Skip to content

Commit

Permalink
fix: checking for style updates of sibling nodes.
Browse files Browse the repository at this point in the history
  • Loading branch information
devjiangzhou committed Jul 4, 2023
1 parent e4b6fc9 commit 7c968aa
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 2 deletions.
25 changes: 23 additions & 2 deletions webf/lib/src/css/query_selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,19 @@ class SelectorEvaluator extends SelectorVisitor {

// http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo
case 'first-child':
if (_element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
}
if (_element!.previousElementSibling != null) {
return _element!.previousElementSibling is HeadElement;
}
return true;

// http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo
case 'last-child':
if (_element!.nextSibling != null && _element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByLastChildRules);
}
return _element!.nextSibling == null;

//http://drafts.csswg.org/selectors-4/#first-of-type-pseudo
Expand All @@ -218,14 +224,23 @@ class SelectorEvaluator extends SelectorVisitor {
var isLast = index == children.length - 1;

if (isFirst && node.name == 'first-of-type') {
if (_element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules);
}
return true;
}

if (isLast && node.name == 'last-of-type') {
if (_element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules);
}
return true;
}

if (isFirst && isLast && node.name == 'only-of-type') {
if (_element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
}
return true;
}

Expand All @@ -235,8 +250,14 @@ class SelectorEvaluator extends SelectorVisitor {
break;
// http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo
case 'only-child':
return _element!.previousSibling == null && _element!.nextSibling == null;

if (_element!.parentElement != null) {
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByLastChildRules);
}
if (_element!.previousSibling == null && _element!.nextSibling == null) {
return true;
}
return false;
// http://dev.w3.org/csswg/selectors-4/#link
case 'link':
return _element!.attributes['href'] != null;
Expand Down
94 changes: 94 additions & 0 deletions webf/lib/src/dom/container_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ import 'package:webf/src/dom/node_traversal.dart';

typedef InsertNodeHandler = void Function(ContainerNode container, Node child, Node? next);

enum DynamicRestyleFlag {
ChildrenAffectedByFirstChildRules,
ChildrenAffectedByLastChildRules,
ChildrenAffectedByDirectAdjacentRules,
ChildrenAffectedByForwardPositionalRules,
ChildrenAffectedByBackwardPositionalRules,
}

extension StructuralRules on DynamicRestyleFlag {
bool childrenAffectedByStructuralRules() {
if (this == DynamicRestyleFlag.ChildrenAffectedByFirstChildRules ||
this == DynamicRestyleFlag.ChildrenAffectedByLastChildRules ||
this == DynamicRestyleFlag.ChildrenAffectedByDirectAdjacentRules ||
this == DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules ||
this == DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules) {
return true;
}
return false;
}
}

bool collectChildrenAndRemoveFromOldParent(Node node, List<Node> nodes) {
if (node is DocumentFragment) {
getChildNodes(node, nodes);
Expand All @@ -34,6 +55,15 @@ void getChildNodes(ContainerNode node, List<Node> nodes) {
abstract class ContainerNode extends Node {
ContainerNode(NodeType nodeType, [BindingContext? context]) : super(nodeType, context);

List<DynamicRestyleFlag>? restyleFlags;

void addFlag(DynamicRestyleFlag flag) {
restyleFlags ??= [];
if (restyleFlags?.contains(flag) == false) {
restyleFlags?.add(flag);
}
}

void _adoptAndAppendChild(ContainerNode container, Node child, Node? next) {
child.parentOrShadowHostNode = this;
if (lastChild != null) {
Expand Down Expand Up @@ -333,6 +363,70 @@ abstract class ContainerNode extends Node {
}
}

void CheckForSiblingStyleChanges(Element parent, bool isRemoved, Node? nodeBeforeChange, Node? nodeAfterChange) {

if (!isRendererAttached) {
return;
}

final elementBeforeChange = nodeBeforeChange as Element?;
final elementAfterChange = nodeAfterChange as Element?;

// :first-child. In the parser callback case, we don't have to check anything, since we were right the first time.
// In the DOM case, we only need to do something if |afterChange| is not 0.
// |afterChange| is 0 in the parser case, so it works out that we'll skip this block.
if (elementAfterChange != null &&
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules) == true) {
// Find our new first child.
final newFirstElement = parent.firstChild as Element?;

// This is the insert/append case.
if (newFirstElement != elementAfterChange && elementAfterChange.isRendererAttached) {
elementAfterChange.recalculateStyle();
}

if (newFirstElement != null && isRemoved && newFirstElement == elementAfterChange) {
newFirstElement.recalculateStyle();
}
}

if (elementBeforeChange != null &&
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByLastChildRules) == true) {
// Find our new first child.
final newLastElement = parent.lastChild as Element?;

// This is the insert/append case.
if (newLastElement != elementBeforeChange && elementBeforeChange.isRendererAttached) {
elementBeforeChange.recalculateStyle();
}

if (newLastElement != null && isRemoved && newLastElement == elementBeforeChange) {
newLastElement.recalculateStyle();
}
}

// The + selector. We need to invalidate the first element following the insertion point. It is the only possible element
// that could be affected by this DOM change.
if (restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByDirectAdjacentRules) == true && elementAfterChange != null) {
elementAfterChange.recalculateStyle();
}

// Forward positional selectors include the ~ selector, nth-child, nth-of-type, first-of-type and only-of-type.
// Backward positional selectors include nth-last-child, nth-last-of-type, last-of-type and only-of-type.
// We have to invalidate everything following the insertion point in the forward case, and everything before the insertion point in the
// backward case.
// |afterChange| is 0 in the parser callback case, so we won't do any work for the forward case if we don't have to.
// For performance reasons we just mark the parent node as changed, since we don't want to make childrenChanged O(n^2) by crawling all our kids
// here. recalcStyle will then force a walk of the children when it sees that this has happened.
if (elementAfterChange != null &&
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules) == true) {
parent.recalculateStyle();
} else if (elementBeforeChange != null &&
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules) == true) {
parent.recalculateStyle();
}
}

Node? _firstChild;

@override
Expand Down
17 changes: 17 additions & 0 deletions webf/lib/src/dom/element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,22 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin
}
}

@override
void childrenChanged(ChildrenChange change) {
super.childrenChanged(change);

if (change.byParser != ChildrenChangeSource.PARSER && change.isChildElementChange()) {
final changedElement = change.siblingChanged as Element?;
final removed = change.type == ChildrenChangeType.ELEMENT_REMOVED;
if (changedElement != null) {
CheckForSiblingStyleChanges(this, removed, change.siblingBeforeChange,
change.siblingAfterChange);
}
} else {
print('123');
}
}

void _updateNameMap(String? newName, {String? oldName}) {
if (oldName != null && oldName.isNotEmpty) {
final elements = ownerDocument.elementsByName[oldName];
Expand Down Expand Up @@ -1787,6 +1803,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin
}

void _applySheetStyle(CSSStyleDeclaration style) {

CSSStyleDeclaration matchRule = _elementRuleCollector.collectionFromRuleSet(ownerDocument.ruleSet, this);
style.union(matchRule);
}
Expand Down

0 comments on commit 7c968aa

Please sign in to comment.