Bug 92965 - Optimize ChildNode{Insertion,Removal}Notifier::notify() by lazily taking a snapshot of child nodes
Summary: Optimize ChildNode{Insertion,Removal}Notifier::notify() by lazily taking a sn...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Kentaro Hara
URL:
Keywords:
Depends on:
Blocks: 88834
  Show dependency treegraph
 
Reported: 2012-08-02 02:45 PDT by Kentaro Hara
Modified: 2012-08-09 21:55 PDT (History)
4 users (show)

See Also:


Attachments
Patch (8.67 KB, patch)
2012-08-02 02:50 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
Patch (8.67 KB, patch)
2012-08-02 02:51 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
Patch (8.77 KB, patch)
2012-08-02 04:30 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
Patch (9.49 KB, patch)
2012-08-02 19:58 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
patch for landing (10.01 KB, patch)
2012-08-07 18:08 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff
patch for landing (9.54 KB, patch)
2012-08-07 18:09 PDT, Kentaro Hara
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Kentaro Hara 2012-08-02 02:45:42 PDT
notifyDescendantRemovedFromDocument() cannot iterate child nodes in this way:

  void notifyDescendantRemovedFromDocument(Node* node) {
    for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
      ...;
      notifyNodeRemovedFromDocument(child);
    }
  }

This is because notifyNodeRemovedFromDocument(child) might dispatch events and the events might change child trees. To avoid security issues, the current code takes a snapshot of child nodes before starting the iteration, like this.

  void notifyDescendantRemovedFromDocument(Node* node) {
     NodeVector children;
     getChildNodes(node, children); // Take a snapshot.
     for (int i = 0; i < children.size(); i++) {
       ...;
       notifyNodeRemovedFromDocument(children[i]);
     }
   }

Based on the observation that in almost all cases events won't be dispatched from inside notifyNodeRemovedFromDocument(), we can implement a "lazy" snapshot. The snapshot is taken at the point where EventDispatcher::dispatchEvent() is invoked. The snapshot is not taken unless any event is dispatched.
Comment 1 Kentaro Hara 2012-08-02 02:50:06 PDT
Created attachment 156020 [details]
Patch
Comment 2 Kentaro Hara 2012-08-02 02:51:10 PDT
Created attachment 156022 [details]
Patch
Comment 3 Kentaro Hara 2012-08-02 04:30:24 PDT
Created attachment 156040 [details]
Patch
Comment 4 Adam Barth 2012-08-02 08:17:13 PDT
Comment on attachment 156040 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=156040&action=review

Nice idea.  Some detailed comments below.

> Source/WebCore/dom/ContainerNode.h:304
> +static ChildNodesSnapshot* latestSnapshot = 0;

Please don't add static global variables to header files.  That will add storage for this variable to every complication unit that includes this header!

> Source/WebCore/dom/ContainerNode.h:324
> +        if (m_childNodes)
> +            delete m_childNodes;

Can we use an OwnPtr to have this delete happen automatically?

> Source/WebCore/dom/ContainerNode.h:338
> +        Node* ret = (*m_childNodes)[m_currentIndex++].get();

ret -> node (prefer complete words in variable names)

> Source/WebCore/dom/ContainerNode.h:344
> +        if (!hasSnapshot()) {

Prefer early return.

> Source/WebCore/dom/ContainerNodeAlgorithms.cpp:69
> +    ChildNodesSnapshot snapshot(node);

I'd put the word "Lazy" into the name of this class to make it clear that the snapshot is happening lazily.
Comment 5 Kentaro Hara 2012-08-02 19:58:14 PDT
Created attachment 156248 [details]
Patch
Comment 6 Kentaro Hara 2012-08-02 19:58:57 PDT
(In reply to comment #3)
> > Source/WebCore/dom/ContainerNode.h:304
> > +static ChildNodesSnapshot* latestSnapshot = 0;
> 
> Please don't add static global variables to header files.  That will add storage for this variable to every complication unit that includes this header!
> 
> > Source/WebCore/dom/ContainerNode.h:324
> > +        if (m_childNodes)
> > +            delete m_childNodes;
> 
> Can we use an OwnPtr to have this delete happen automatically?
> 
> > Source/WebCore/dom/ContainerNode.h:338
> > +        Node* ret = (*m_childNodes)[m_currentIndex++].get();
> 
> ret -> node (prefer complete words in variable names)
> 
> > Source/WebCore/dom/ContainerNode.h:344
> > +        if (!hasSnapshot()) {
> 
> Prefer early return.
> 
> > Source/WebCore/dom/ContainerNodeAlgorithms.cpp:69
> > +    ChildNodesSnapshot snapshot(node);
> 
> I'd put the word "Lazy" into the name of this class to make it clear that the snapshot is happening lazily.

All done. Thanks.
Comment 7 Adam Barth 2012-08-07 17:01:44 PDT
Comment on attachment 156248 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=156248&action=review

> Source/WebCore/dom/ContainerNode.h:308
> +    ChildNodesLazySnapshot(Node* parentNode)

explicit

> Source/WebCore/dom/ContainerNode.h:325
> +        if (LIKELY(!m_childNodes.get())) {

Can we call hasSnapshot() here?  That would be slightly more readable.
Comment 8 Kentaro Hara 2012-08-07 18:08:03 PDT
Created attachment 157065 [details]
patch for landing
Comment 9 Kentaro Hara 2012-08-07 18:09:35 PDT
Created attachment 157066 [details]
patch for landing
Comment 10 WebKit Review Bot 2012-08-07 22:38:51 PDT
Comment on attachment 157066 [details]
patch for landing

Clearing flags on attachment: 157066

Committed r124990: <http://trac.webkit.org/changeset/124990>
Comment 11 Ojan Vafai 2012-08-08 11:59:36 PDT
Could we do this same trick for the methods in ContainerNode that call collectChildrenAndRemoveFromOldParent and/or collectTargetNodes? It might have a performance benefit for large innerHTML calls for example.
Comment 12 Kentaro Hara 2012-08-08 16:39:03 PDT
(In reply to comment #11)
> Could we do this same trick for the methods in ContainerNode that call collectChildrenAndRemoveFromOldParent and/or collectTargetNodes? It might have a performance benefit for large innerHTML calls for example.

Yes, I'll do after investigating performance and security stuff.