Changes between Initial Version and Version 1 of TraversingShadowDOMTree


Ignore:
Timestamp:
May 8, 2012 6:05:44 PM (12 years ago)
Author:
hayato@chromium.org
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • TraversingShadowDOMTree

    v1 v1  
     1
     2= Traversing Shadow DOM Tree =
     3
     4This page explains how to use [https://trac.webkit.org/browser/trunk/Source/WebCore/dom/ComposedShadowTreeWalker.cpp ComposedShadowTreeWalker] ComposedShadowTreeWalker class, which enables you to traverse nodes in composed shadow tree order.
     5
     6= Background =
     7
     8A composed shadow tree is a result of applying a node distribution algorithm described in [http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#shadow-dom-subtrees Shadow DOM spec]. That’s ‘one big tree’ used in rendering. There are some use cases other than rendering where we want to traverse nodes in composed shadow tree order, not original DOM tree order. To achieve that, instead of creating composed shadow tree in memory physically, I’ve decided to provide APIs which enable you to traverse nodes in composed shadow tree order as if that were physically constructed in memory. You don’t have to know any background structure of composed shadow tree as long as you use the APIs.
     9
     10= Examples =
     11
     12Suppose we are given the following DOM tree:
     13
     14{{{
     15A
     16└──B─────[SR1]
     17   ├─C     └──G──────────────[SR2]
     18   │ └─D      ├─<H select=C>    ├─<K select=H,J>
     19   └─E        ├─I               ├─L
     20     └─F      └─J               └─<M select=None>
     21                                   └─N
     22
     23  [SR]: ShadowRoot
     24  <X select=Y>: InsertionPoint, called X, which selects Y.
     25}}}
     26
     27Applying a node distribution algorithm to the given DOM tree, a composed shadow tree would be:
     28
     29{{{
     30A
     31└──B
     32   │
     33  [SR1]
     34   │
     35   └─G
     36     │
     37    [SR2]
     38     │
     39     ├─C
     40     │ └─D
     41     ├─J
     42     ├─L
     43     └─N
     44}}}
     45
     46
     47= API Usage =
     48
     49If you want to traverse nodes in composed shadow tree order, starting node A, use the following idiom:
     50
     51{{{
     52for (ComposedShadowDOMTreeWalker walker(nodeA); walker.get(); walker.next()) {
     53    printNode(walker.get())
     54}
     55}}}
     56
     57This will print nodes in the following order: A, B, G, C, D, J, L, N
     58
     59ComposedShadowTreeWalker receives a starting node in its constructor. Other than next() member function, ComposedShadowTreeWalker provides other traversal functions such as:
     60
     61  * walker.previous()
     62  * walker.parent()
     63  * walker.nextSibling()
     64  * walker.previousSibling()
     65  * walker.firstChild()
     66  * walker.lastChild()
     67
     68These provide similar functionality to functions defined in WebCore::Node, but will traverse  composed shadow tree order, not original DOM tree order.
     69These function doesn’t return the result node. All returns ‘void’. Instead, these will update the walker’s internal state, a current visited node. You can get the current visited node by walker.get() function.
     70
     71The following is the example of traversing.
     72
     73
     74||  || A  ||   B ||  C ||  E ||  G ||  I ||  J ||  L ||  N ||
     75||next() || B ||  G ||  D ||  F ||  C ||  null ||   L ||  N ||  null ||
     76||previous() || null ||   A ||  null ||   null ||    B ||  null ||   D ||  J ||  L ||
     77||firstChild() ||  B ||  G  ||   D ||   F ||  C  ||  null || null || null ||  null ||
     78||parent() ||           null   ||         A   ||    G ||  null ||   B  ||     null ||   G   ||    G ||  G ||
     79||nextSibling()  ||      null  ||          null ||   J ||  null ||   null ||   null ||   L  ||     N ||  null ||
     80
     81
     82Notes:
     831. Some nodes (E, F and I) do not participate in the composed shadow tree because they are children (or descendants) of shadow host, but are not distributed. These nodes are treated as separated ‘islands’. e.g. E’s parent() is null, not B.
     842. You could not pass an active InsertionPoint (H, K and M) or a ShadowRoot as a starting node. That should trigger an assertion error.
     85
     86
     87= Traverse Policy =
     88
     89There are some use cases where we want to disallow walker to cross an upper shadow boundary. We can change ‘traversing policy’ by passing Policy to ComposedShadowTreeWalker’s constructor. For example, if we want to traverse composed shadow tree, resolving InsertionPoints automatically, but don’t want to cross upper boundary, you can create a walker like this:
     90
     91{{{
     92ComposedShadowTreeWalker walker(startingNode, ComposedShadowTreeWalker::DoNotCrossUpperBoundary)
     93}}}
     94
     95If a walker is constructed with a policy of DoNotCrossUpperBoundary, youngest ShadowRoots will be also a candidate of visiting nodes and won’t be skipped. Youngest ShadowRoots will act as a top-most node in scope, like a Document node in original DOM tree.
     96
     97The following is the result in case of DoNotCrossUpperBoundary policy:
     98
     99||  || B  || SR1 ||  G || SR2 || C ||
     100|| firstChild() || null ||  G ||  null ||   C ||  D ||
     101|| parent() ||     A  ||    null  ||     SR1 || null ||   SR2 ||
     102|| next()   ||     null ||  G   ||       null  ||      C ||  D ||
     103|| previous() ||   A  ||    null ||      SR1   ||      null ||   SR2 ||
     104
     105= Hosting Multiple Shadow Roots =
     106
     107Although I omitted the hosting multiple shadow roots case from the example, ComposedShadowTreeWalker supports multiple shadow roots. <shadow> elements are resolved transparently.
     108
     109= Error cases =
     110
     111* In Policy of CrossBounary (default):
     112You cannot pass an active InsertionPoint or ShadowRoot as a starting node.
     113
     114* In Policy of DoNotCrossUpperBoundary
     115You cannot pass an active InsertionPoint or non-youngest ShadowRoot as a starting node.
     116
     117You cannot call any traversal functions if the current visited node is null.
     118
     119= Performance =
     120
     121To be compared to Node::traverseNextNode() function, walker::next() is about 6 times slower. The result of a micro benchmark is here:
     122https://bugs.webkit.org/show_bug.cgi?id=79197#c33
     123* The result is based on old APIs. The current walker is a bit faster than old APIs.
     124I am going to improve the performance here: https://bugs.webkit.org/show_bug.cgi?id=82702
     125
     126= Actual Clients =
     127
     128== Focus Controller ==
     129
     130FocusController (*1) started to use ComposedShadowTreeWalker to traverse composed shadow DOM tree. That’s good example about how to use APIs. That also uses DoNotCrossUpperBoundary policy since Focus navigation should be scoped in each Shadow DOM subtree according to the current Shadow DOM spec. Although FocusNavigation is complex since it should consider each shadow DOM subteee as a ‘scope’, the APIs made implementations much easier since much complex works are done within APIs.
     131
     132*1: https://trac.webkit.org/browser/trunk/Source/WebCore/page/FocusController.cpp
     133
     134== Event Dispatcher ==
     135
     136EventDispatcher will be the next client of the APIs. Work-in-progress patch is here:
     137https://bugs.webkit.org/show_bug.cgi?id=78586