Changeset 201778 in webkit


Ignore:
Timestamp:
Jun 7, 2016 5:32:08 PM (8 years ago)
Author:
BJ Burg
Message:

Web Inspector: reduce timer churn when processing many DOM.attributeModified messages
https://bugs.webkit.org/show_bug.cgi?id=158491
<rdar://problem/25561452>

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.

Location:
trunk/Source/WebInspectorUI
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r201773 r201778  
     12016-06-07  Brian Burg  <bburg@apple.com>
     2
     3        Web Inspector: reduce timer churn when processing many DOM.attributeModified messages
     4        https://bugs.webkit.org/show_bug.cgi?id=158491
     5        <rdar://problem/25561452>
     6
     7        Reviewed by Timothy Hatcher.
     8
     9        When the backend sends thousands of DOM.attributeModified events to the frontend, it
     10        slows to a crawl. This is partly because redundant messages are being sent, and
     11        because the frontend is taking too long to render attribute updates in the elements tab.
     12
     13        This patch is a first step to improve performance by reducing unnecessary work. It
     14        coalesces all attribute state updates to only happen once per animation frame. This
     15        reduces timer churn because we previously used a debouncing timer with interval of 0ms,
     16        and that had to be cleared and restarted on every call. This change also eliminates
     17        forced layouts when updating the selection highlights, since the DOM tree outline has
     18        been reflowed by the time we start updating selections in a requestAnimationFrame callback.
     19
     20        There is still a lot of optimization to be done here, but this reduces the problem
     21        considerably by keeping the event loop clear and making it obvious which selection
     22        update operations are still too expensive.
     23
     24        * UserInterface/Base/Utilities.js:
     25        Add a 'onNextFrame' proxy to Object. It works like debounce, except it coalesces calls
     26        up until the next animation frame rather than a fixed timeout. It also does not extend
     27        the timeout interval for each call.
     28
     29        * UserInterface/Views/DOMTreeUpdater.js:
     30        (WebInspector.DOMTreeUpdater.prototype._attributesUpdated):
     31        (WebInspector.DOMTreeUpdater.prototype._characterDataModified):
     32        (WebInspector.DOMTreeUpdater.prototype._nodeInserted):
     33        (WebInspector.DOMTreeUpdater.prototype._nodeRemoved):
     34        (WebInspector.DOMTreeUpdater.prototype._updateModifiedNodes):
     35        Update on the next frame rather than on a zero delay timeout.
     36
     37        * UserInterface/Views/TreeOutline.js:
     38        (WebInspector.TreeOutline.WebInspector.TreeElement.prototype.didChange):
     39        (WebInspector.TreeOutline.WebInspector.TreeElement.prototype._fireDidChange):
     40        Update on the next frame rather than on a zero delay timeout.
     41
    1422016-06-07  Nikita Vasilyev  <nvasilyev@apple.com>
    243
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r201168 r201778  
    12741274        }
    12751275    });
     1276
     1277    const requestAnimationFrameSymbol = Symbol("peform-on-animation-frame");
     1278    const requestAnimationFrameProxySymbol = Symbol("perform-on-animation-frame-proxy");
     1279
     1280    Object.defineProperty(Object.prototype, "onNextFrame",
     1281    {
     1282        get: function()
     1283        {
     1284            if (!this[requestAnimationFrameProxySymbol]) {
     1285                this[requestAnimationFrameProxySymbol] = new Proxy(this, {
     1286                    get(target, property, receiver) {
     1287                        return (...args) => {
     1288                            let original = target[property];
     1289                            console.assert(typeof original === "function");
     1290
     1291                            if (original[requestAnimationFrameSymbol])
     1292                                return;
     1293
     1294                            let performWork = () => {
     1295                                original[requestAnimationFrameSymbol] = undefined;
     1296                                original.apply(target, args);
     1297                            };
     1298
     1299                            original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork);
     1300                        };
     1301                    }
     1302                });
     1303            }
     1304
     1305            return this[requestAnimationFrameProxySymbol];
     1306        }
     1307    });
    12761308})();
    12771309
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js

    r199790 r201778  
    5858        this._recentlyModifiedNodes.push({node: event.data.node, updated: true, attribute: event.data.name});
    5959        if (this._treeOutline._visible)
    60             this.soon._updateModifiedNodes();
     60            this.onNextFrame._updateModifiedNodes();
    6161    },
    6262
     
    6565        this._recentlyModifiedNodes.push({node: event.data.node, updated: true});
    6666        if (this._treeOutline._visible)
    67             this.soon._updateModifiedNodes();
     67            this.onNextFrame._updateModifiedNodes();
    6868    },
    6969
     
    7272        this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, inserted: true});
    7373        if (this._treeOutline._visible)
    74             this.soon._updateModifiedNodes();
     74            this.onNextFrame._updateModifiedNodes();
    7575    },
    7676
     
    7979        this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, removed: true});
    8080        if (this._treeOutline._visible)
    81             this.soon._updateModifiedNodes();
     81            this.onNextFrame._updateModifiedNodes();
    8282    },
    8383
     
    9191    _updateModifiedNodes: function()
    9292    {
    93         this._updateModifiedNodes.cancelDebounce();
    94 
    9593        let updatedParentTreeElements = [];
    9694        for (let recentlyModifiedNode of this._recentlyModifiedNodes) {
  • trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js

    r196362 r201778  
    801801    _fireDidChange()
    802802    {
    803         this._didChangeTimeoutIdentifier = undefined;
    804 
    805803        if (this.treeOutline)
    806804            this.treeOutline._treeElementDidChange(this);
     
    812810            return;
    813811
    814         // Prevent telling the TreeOutline multiple times in a row by delaying it with a timeout.
    815         if (!this._didChangeTimeoutIdentifier)
    816             this._didChangeTimeoutIdentifier = setTimeout(this._fireDidChange.bind(this), 0);
     812        this.onNextFrame._fireDidChange();
    817813    }
    818814
Note: See TracChangeset for help on using the changeset viewer.