Skip to content

Commit 7c968aa

Browse files
committed
fix: checking for style updates of sibling nodes.
1 parent e4b6fc9 commit 7c968aa

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed

webf/lib/src/css/query_selector.dart

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,19 @@ class SelectorEvaluator extends SelectorVisitor {
192192

193193
// http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo
194194
case 'first-child':
195+
if (_element!.parentElement != null) {
196+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
197+
}
195198
if (_element!.previousElementSibling != null) {
196199
return _element!.previousElementSibling is HeadElement;
197200
}
198201
return true;
199202

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

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

220226
if (isFirst && node.name == 'first-of-type') {
227+
if (_element!.parentElement != null) {
228+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules);
229+
}
221230
return true;
222231
}
223232

224233
if (isLast && node.name == 'last-of-type') {
234+
if (_element!.parentElement != null) {
235+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules);
236+
}
225237
return true;
226238
}
227239

228240
if (isFirst && isLast && node.name == 'only-of-type') {
241+
if (_element!.parentElement != null) {
242+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
243+
}
229244
return true;
230245
}
231246

@@ -235,8 +250,14 @@ class SelectorEvaluator extends SelectorVisitor {
235250
break;
236251
// http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo
237252
case 'only-child':
238-
return _element!.previousSibling == null && _element!.nextSibling == null;
239-
253+
if (_element!.parentElement != null) {
254+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules);
255+
_element!.parentElement!.addFlag(DynamicRestyleFlag.ChildrenAffectedByLastChildRules);
256+
}
257+
if (_element!.previousSibling == null && _element!.nextSibling == null) {
258+
return true;
259+
}
260+
return false;
240261
// http://dev.w3.org/csswg/selectors-4/#link
241262
case 'link':
242263
return _element!.attributes['href'] != null;

webf/lib/src/dom/container_node.dart

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ import 'package:webf/src/dom/node_traversal.dart';
1010

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

13+
enum DynamicRestyleFlag {
14+
ChildrenAffectedByFirstChildRules,
15+
ChildrenAffectedByLastChildRules,
16+
ChildrenAffectedByDirectAdjacentRules,
17+
ChildrenAffectedByForwardPositionalRules,
18+
ChildrenAffectedByBackwardPositionalRules,
19+
}
20+
21+
extension StructuralRules on DynamicRestyleFlag {
22+
bool childrenAffectedByStructuralRules() {
23+
if (this == DynamicRestyleFlag.ChildrenAffectedByFirstChildRules ||
24+
this == DynamicRestyleFlag.ChildrenAffectedByLastChildRules ||
25+
this == DynamicRestyleFlag.ChildrenAffectedByDirectAdjacentRules ||
26+
this == DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules ||
27+
this == DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules) {
28+
return true;
29+
}
30+
return false;
31+
}
32+
}
33+
1334
bool collectChildrenAndRemoveFromOldParent(Node node, List<Node> nodes) {
1435
if (node is DocumentFragment) {
1536
getChildNodes(node, nodes);
@@ -34,6 +55,15 @@ void getChildNodes(ContainerNode node, List<Node> nodes) {
3455
abstract class ContainerNode extends Node {
3556
ContainerNode(NodeType nodeType, [BindingContext? context]) : super(nodeType, context);
3657

58+
List<DynamicRestyleFlag>? restyleFlags;
59+
60+
void addFlag(DynamicRestyleFlag flag) {
61+
restyleFlags ??= [];
62+
if (restyleFlags?.contains(flag) == false) {
63+
restyleFlags?.add(flag);
64+
}
65+
}
66+
3767
void _adoptAndAppendChild(ContainerNode container, Node child, Node? next) {
3868
child.parentOrShadowHostNode = this;
3969
if (lastChild != null) {
@@ -333,6 +363,70 @@ abstract class ContainerNode extends Node {
333363
}
334364
}
335365

366+
void CheckForSiblingStyleChanges(Element parent, bool isRemoved, Node? nodeBeforeChange, Node? nodeAfterChange) {
367+
368+
if (!isRendererAttached) {
369+
return;
370+
}
371+
372+
final elementBeforeChange = nodeBeforeChange as Element?;
373+
final elementAfterChange = nodeAfterChange as Element?;
374+
375+
// :first-child. In the parser callback case, we don't have to check anything, since we were right the first time.
376+
// In the DOM case, we only need to do something if |afterChange| is not 0.
377+
// |afterChange| is 0 in the parser case, so it works out that we'll skip this block.
378+
if (elementAfterChange != null &&
379+
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByFirstChildRules) == true) {
380+
// Find our new first child.
381+
final newFirstElement = parent.firstChild as Element?;
382+
383+
// This is the insert/append case.
384+
if (newFirstElement != elementAfterChange && elementAfterChange.isRendererAttached) {
385+
elementAfterChange.recalculateStyle();
386+
}
387+
388+
if (newFirstElement != null && isRemoved && newFirstElement == elementAfterChange) {
389+
newFirstElement.recalculateStyle();
390+
}
391+
}
392+
393+
if (elementBeforeChange != null &&
394+
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByLastChildRules) == true) {
395+
// Find our new first child.
396+
final newLastElement = parent.lastChild as Element?;
397+
398+
// This is the insert/append case.
399+
if (newLastElement != elementBeforeChange && elementBeforeChange.isRendererAttached) {
400+
elementBeforeChange.recalculateStyle();
401+
}
402+
403+
if (newLastElement != null && isRemoved && newLastElement == elementBeforeChange) {
404+
newLastElement.recalculateStyle();
405+
}
406+
}
407+
408+
// The + selector. We need to invalidate the first element following the insertion point. It is the only possible element
409+
// that could be affected by this DOM change.
410+
if (restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByDirectAdjacentRules) == true && elementAfterChange != null) {
411+
elementAfterChange.recalculateStyle();
412+
}
413+
414+
// Forward positional selectors include the ~ selector, nth-child, nth-of-type, first-of-type and only-of-type.
415+
// Backward positional selectors include nth-last-child, nth-last-of-type, last-of-type and only-of-type.
416+
// We have to invalidate everything following the insertion point in the forward case, and everything before the insertion point in the
417+
// backward case.
418+
// |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.
419+
// 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
420+
// here. recalcStyle will then force a walk of the children when it sees that this has happened.
421+
if (elementAfterChange != null &&
422+
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByForwardPositionalRules) == true) {
423+
parent.recalculateStyle();
424+
} else if (elementBeforeChange != null &&
425+
restyleFlags?.contains(DynamicRestyleFlag.ChildrenAffectedByBackwardPositionalRules) == true) {
426+
parent.recalculateStyle();
427+
}
428+
}
429+
336430
Node? _firstChild;
337431

338432
@override

webf/lib/src/dom/element.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,22 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin
11841184
}
11851185
}
11861186

1187+
@override
1188+
void childrenChanged(ChildrenChange change) {
1189+
super.childrenChanged(change);
1190+
1191+
if (change.byParser != ChildrenChangeSource.PARSER && change.isChildElementChange()) {
1192+
final changedElement = change.siblingChanged as Element?;
1193+
final removed = change.type == ChildrenChangeType.ELEMENT_REMOVED;
1194+
if (changedElement != null) {
1195+
CheckForSiblingStyleChanges(this, removed, change.siblingBeforeChange,
1196+
change.siblingAfterChange);
1197+
}
1198+
} else {
1199+
print('123');
1200+
}
1201+
}
1202+
11871203
void _updateNameMap(String? newName, {String? oldName}) {
11881204
if (oldName != null && oldName.isNotEmpty) {
11891205
final elements = ownerDocument.elementsByName[oldName];
@@ -1787,6 +1803,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin
17871803
}
17881804

17891805
void _applySheetStyle(CSSStyleDeclaration style) {
1806+
17901807
CSSStyleDeclaration matchRule = _elementRuleCollector.collectionFromRuleSet(ownerDocument.ruleSet, this);
17911808
style.union(matchRule);
17921809
}

0 commit comments

Comments
 (0)