<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[201778] trunk/Source/WebInspectorUI</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/201778">201778</a></dd>
<dt>Author</dt> <dd>bburg@apple.com</dd>
<dt>Date</dt> <dd>2016-06-07 17:32:08 -0700 (Tue, 07 Jun 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Web Inspector: reduce timer churn when processing many DOM.attributeModified messages
https://bugs.webkit.org/show_bug.cgi?id=158491
&lt;rdar://problem/25561452&gt;

Reviewed by Timothy Hatcher.

When the backend sends thousands of DOM.attributeModified events to the frontend, it
slows to a crawl. This is partly because redundant messages are being sent, and
because the frontend is taking too long to render attribute updates in the elements tab.

This patch is a first step to improve performance by reducing unnecessary work. It
coalesces all attribute state updates to only happen once per animation frame. This
reduces timer churn because we previously used a debouncing timer with interval of 0ms,
and that had to be cleared and restarted on every call. This change also eliminates
forced layouts when updating the selection highlights, since the DOM tree outline has
been reflowed by the time we start updating selections in a requestAnimationFrame callback.

There is still a lot of optimization to be done here, but this reduces the problem
considerably by keeping the event loop clear and making it obvious which selection
update operations are still too expensive.

* UserInterface/Base/Utilities.js:
Add a 'onNextFrame' proxy to Object. It works like debounce, except it coalesces calls
up until the next animation frame rather than a fixed timeout. It also does not extend
the timeout interval for each call.

* UserInterface/Views/DOMTreeUpdater.js:
(WebInspector.DOMTreeUpdater.prototype._attributesUpdated):
(WebInspector.DOMTreeUpdater.prototype._characterDataModified):
(WebInspector.DOMTreeUpdater.prototype._nodeInserted):
(WebInspector.DOMTreeUpdater.prototype._nodeRemoved):
(WebInspector.DOMTreeUpdater.prototype._updateModifiedNodes):
Update on the next frame rather than on a zero delay timeout.

* UserInterface/Views/TreeOutline.js:
(WebInspector.TreeOutline.WebInspector.TreeElement.prototype.didChange):
(WebInspector.TreeOutline.WebInspector.TreeElement.prototype._fireDidChange):
Update on the next frame rather than on a zero delay timeout.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs">trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeUpdaterjs">trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsTreeOutlinejs">trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (201777 => 201778)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/ChangeLog        2016-06-08 00:06:28 UTC (rev 201777)
+++ trunk/Source/WebInspectorUI/ChangeLog        2016-06-08 00:32:08 UTC (rev 201778)
</span><span class="lines">@@ -1,3 +1,44 @@
</span><ins>+2016-06-07  Brian Burg  &lt;bburg@apple.com&gt;
+
+        Web Inspector: reduce timer churn when processing many DOM.attributeModified messages
+        https://bugs.webkit.org/show_bug.cgi?id=158491
+        &lt;rdar://problem/25561452&gt;
+
+        Reviewed by Timothy Hatcher.
+
+        When the backend sends thousands of DOM.attributeModified events to the frontend, it
+        slows to a crawl. This is partly because redundant messages are being sent, and
+        because the frontend is taking too long to render attribute updates in the elements tab.
+
+        This patch is a first step to improve performance by reducing unnecessary work. It
+        coalesces all attribute state updates to only happen once per animation frame. This
+        reduces timer churn because we previously used a debouncing timer with interval of 0ms,
+        and that had to be cleared and restarted on every call. This change also eliminates
+        forced layouts when updating the selection highlights, since the DOM tree outline has
+        been reflowed by the time we start updating selections in a requestAnimationFrame callback.
+
+        There is still a lot of optimization to be done here, but this reduces the problem
+        considerably by keeping the event loop clear and making it obvious which selection
+        update operations are still too expensive.
+
+        * UserInterface/Base/Utilities.js:
+        Add a 'onNextFrame' proxy to Object. It works like debounce, except it coalesces calls
+        up until the next animation frame rather than a fixed timeout. It also does not extend
+        the timeout interval for each call.
+
+        * UserInterface/Views/DOMTreeUpdater.js:
+        (WebInspector.DOMTreeUpdater.prototype._attributesUpdated):
+        (WebInspector.DOMTreeUpdater.prototype._characterDataModified):
+        (WebInspector.DOMTreeUpdater.prototype._nodeInserted):
+        (WebInspector.DOMTreeUpdater.prototype._nodeRemoved):
+        (WebInspector.DOMTreeUpdater.prototype._updateModifiedNodes):
+        Update on the next frame rather than on a zero delay timeout.
+
+        * UserInterface/Views/TreeOutline.js:
+        (WebInspector.TreeOutline.WebInspector.TreeElement.prototype.didChange):
+        (WebInspector.TreeOutline.WebInspector.TreeElement.prototype._fireDidChange):
+        Update on the next frame rather than on a zero delay timeout.
+
</ins><span class="cx"> 2016-06-07  Nikita Vasilyev  &lt;nvasilyev@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         REGRESSION (r158219): Web Inspector: Border under Memory content view is too thick
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (201777 => 201778)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js        2016-06-08 00:06:28 UTC (rev 201777)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js        2016-06-08 00:32:08 UTC (rev 201778)
</span><span class="lines">@@ -1273,6 +1273,38 @@
</span><span class="cx">             this[debounceTimeoutSymbol] = undefined;
</span><span class="cx">         }
</span><span class="cx">     });
</span><ins>+
+    const requestAnimationFrameSymbol = Symbol(&quot;peform-on-animation-frame&quot;);
+    const requestAnimationFrameProxySymbol = Symbol(&quot;perform-on-animation-frame-proxy&quot;);
+
+    Object.defineProperty(Object.prototype, &quot;onNextFrame&quot;,
+    {
+        get: function()
+        {
+            if (!this[requestAnimationFrameProxySymbol]) {
+                this[requestAnimationFrameProxySymbol] = new Proxy(this, {
+                    get(target, property, receiver) {
+                        return (...args) =&gt; {
+                            let original = target[property];
+                            console.assert(typeof original === &quot;function&quot;);
+
+                            if (original[requestAnimationFrameSymbol])
+                                return;
+
+                            let performWork = () =&gt; {
+                                original[requestAnimationFrameSymbol] = undefined;
+                                original.apply(target, args);
+                            };
+
+                            original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork);
+                        };
+                    }
+                });
+            }
+
+            return this[requestAnimationFrameProxySymbol];
+        }
+    });
</ins><span class="cx"> })();
</span><span class="cx"> 
</span><span class="cx"> function appendWebInspectorSourceURL(string)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeUpdaterjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js (201777 => 201778)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js        2016-06-08 00:06:28 UTC (rev 201777)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js        2016-06-08 00:32:08 UTC (rev 201778)
</span><span class="lines">@@ -57,28 +57,28 @@
</span><span class="cx">     {
</span><span class="cx">         this._recentlyModifiedNodes.push({node: event.data.node, updated: true, attribute: event.data.name});
</span><span class="cx">         if (this._treeOutline._visible)
</span><del>-            this.soon._updateModifiedNodes();
</del><ins>+            this.onNextFrame._updateModifiedNodes();
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     _characterDataModified: function(event)
</span><span class="cx">     {
</span><span class="cx">         this._recentlyModifiedNodes.push({node: event.data.node, updated: true});
</span><span class="cx">         if (this._treeOutline._visible)
</span><del>-            this.soon._updateModifiedNodes();
</del><ins>+            this.onNextFrame._updateModifiedNodes();
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     _nodeInserted: function(event)
</span><span class="cx">     {
</span><span class="cx">         this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, inserted: true});
</span><span class="cx">         if (this._treeOutline._visible)
</span><del>-            this.soon._updateModifiedNodes();
</del><ins>+            this.onNextFrame._updateModifiedNodes();
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     _nodeRemoved: function(event)
</span><span class="cx">     {
</span><span class="cx">         this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, removed: true});
</span><span class="cx">         if (this._treeOutline._visible)
</span><del>-            this.soon._updateModifiedNodes();
</del><ins>+            this.onNextFrame._updateModifiedNodes();
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     _childNodeCountUpdated: function(event)
</span><span class="lines">@@ -90,8 +90,6 @@
</span><span class="cx"> 
</span><span class="cx">     _updateModifiedNodes: function()
</span><span class="cx">     {
</span><del>-        this._updateModifiedNodes.cancelDebounce();
-
</del><span class="cx">         let updatedParentTreeElements = [];
</span><span class="cx">         for (let recentlyModifiedNode of this._recentlyModifiedNodes) {
</span><span class="cx">             let parent = recentlyModifiedNode.parent;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsTreeOutlinejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js (201777 => 201778)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js        2016-06-08 00:06:28 UTC (rev 201777)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js        2016-06-08 00:32:08 UTC (rev 201778)
</span><span class="lines">@@ -800,8 +800,6 @@
</span><span class="cx"> 
</span><span class="cx">     _fireDidChange()
</span><span class="cx">     {
</span><del>-        this._didChangeTimeoutIdentifier = undefined;
-
</del><span class="cx">         if (this.treeOutline)
</span><span class="cx">             this.treeOutline._treeElementDidChange(this);
</span><span class="cx">     }
</span><span class="lines">@@ -811,9 +809,7 @@
</span><span class="cx">         if (!this.treeOutline)
</span><span class="cx">             return;
</span><span class="cx"> 
</span><del>-        // Prevent telling the TreeOutline multiple times in a row by delaying it with a timeout.
-        if (!this._didChangeTimeoutIdentifier)
-            this._didChangeTimeoutIdentifier = setTimeout(this._fireDidChange.bind(this), 0);
</del><ins>+        this.onNextFrame._fireDidChange();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _setListItemNodeContent()
</span></span></pre>
</div>
</div>

</body>
</html>