WebKit Bugzilla
Attachment 362178 Details for
Bug 194721
: Web Inspector: make debounce Proxy into its own class
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-194721-20190215162622.patch (text/plain), 68.61 KB, created by
Devin Rousso
on 2019-02-15 16:26:27 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Devin Rousso
Created:
2019-02-15 16:26:27 PST
Size:
68.61 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index 880666f9cb340d060e7e7c1a6be9f993a26aebaf..e2ae8d0bc0821c32baca5889cd7a6a1b0e89d8a1 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,92 @@ >+2019-02-15 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: make debounce Proxy into its own class >+ https://bugs.webkit.org/show_bug.cgi?id=194721 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ The `debounce`/`throttle` functions leveraged a `Proxy` to intercept the function call and >+ replace it with a "delayed" version. The issue with this is that it set the identifier for >+ the timer on the function itself, which is shared across all instances of a given class. >+ When different instances call the same delayed function, they'd clobber eachother's attempts >+ to delay work. >+ >+ * UserInterface/Base/Debouncer.js: Added. >+ (Debouncer): >+ (Debouncer.prototype.force): >+ (Debouncer.prototype.delayForTime): >+ (Debouncer.prototype.delayForFrame): >+ (Debouncer.prototype.delayForMicrotask): >+ (Debouncer.prototype.cancel): >+ (Debouncer.prototype._execute): >+ * UserInterface/Base/Throttler.js: Added. >+ (Throttler): >+ (Throttler.prototype.force): >+ (Throttler.prototype.fire): >+ (Throttler.prototype.cancel): >+ (Throttler.prototype._execute): >+ * UserInterface/Base/Utilities.js: >+ >+ * UserInterface/Views/BezierEditor.js: >+ (WI.BezierEditor): >+ * UserInterface/Views/ContentBrowser.js: >+ (WI.ContentBrowser): >+ (WI.ContentBrowser.prototype._contentViewSelectionPathComponentDidChange): >+ (WI.ContentBrowser.prototype._contentViewSupplementalRepresentedObjectsDidChange): >+ (WI.ContentBrowser.prototype._currentContentViewDidChange): >+ (WI.ContentBrowser.prototype._dispatchCurrentRepresentedObjectsDidChangeEvent): Deleted. >+ * UserInterface/Views/DOMTreeUpdater.js: >+ (WI.DOMTreeUpdater): >+ (WI.DOMTreeUpdater.prototype._nodeAttributeModified): >+ (WI.DOMTreeUpdater.prototype._nodeInserted): >+ (WI.DOMTreeUpdater.prototype._nodeRemoved): >+ * UserInterface/Views/NavigationSidebarPanel.js: >+ (WI.NavigationSidebarPanel): >+ (WI.NavigationSidebarPanel.prototype.closed): >+ (WI.NavigationSidebarPanel.prototype.showEmptyContentPlaceholder): >+ (WI.NavigationSidebarPanel.prototype.hideEmptyContentPlaceholder): >+ (WI.NavigationSidebarPanel.prototype.updateFilter): >+ (WI.NavigationSidebarPanel.prototype.shown): >+ (WI.NavigationSidebarPanel.prototype._updateContentOverflowShadowVisibility): >+ (WI.NavigationSidebarPanel.prototype._treeElementAddedOrChanged): >+ (WI.NavigationSidebarPanel.prototype._treeElementDisclosureDidChange): >+ * UserInterface/Views/RecordingContentView.js: >+ (WI.RecordingContentView): >+ (WI.RecordingContentView.prototype.updateActionIndex): >+ (WI.RecordingContentView.prototype.hidden): >+ * UserInterface/Views/ResourceDetailsSidebarPanel.js: >+ (WI.ResourceDetailsSidebarPanel.prototype._refreshRelatedResourcesSection): >+ (WI.ResourceDetailsSidebarPanel.prototype._applyResourceEventListeners): >+ (WI.ResourceDetailsSidebarPanel): >+ * UserInterface/Views/ShaderProgramContentView.js: >+ (WI.ShaderProgramContentView): >+ * UserInterface/Views/SpringEditor.js: >+ (WI.SpringEditor.prototype._resetPreviewAnimation): >+ * UserInterface/Views/TreeElement.js: >+ (WI.TreeElement.prototype.set hidden): >+ (WI.TreeElement.prototype.didChange): >+ (WI.TreeElement.prototype._attach): >+ (WI.TreeElement.prototype._detach): >+ (WI.TreeElement.prototype.collapse): >+ (WI.TreeElement.prototype.expand): >+ (WI.TreeElement.prototype.reveal): >+ * UserInterface/Views/TreeOutline.js: >+ (WI.TreeOutline): >+ (WI.TreeOutline.prototype.registerScrollVirtualizer): >+ (WI.TreeOutline.prototype.get updateVirtualizedElementsDebouncer): Added. >+ (WI.TreeOutline.prototype._updateVirtualizedElements): Added. >+ (WI.TreeOutline.prototype.updateVirtualizedElements.walk): Deleted. >+ (WI.TreeOutline.prototype.updateVirtualizedElements): Deleted. >+ * UserInterface/Views/WebSocketContentView.js: >+ (WI.WebSocketContentView): >+ (WI.WebSocketContentView.prototype.shown): >+ (WI.WebSocketContentView.prototype._updateFramesSoon): >+ (WI.WebSocketContentView.prototype._updateFrames): >+ >+ * UserInterface/Main.html: >+ * UserInterface/Test.html: >+ * .eslintrc: >+ > 2019-02-15 Devin Rousso <drousso@apple.com> > > Web Inspector: Canvas: all actions after an offscreen path modification are marked as offscreen path errors >diff --git a/Source/WebInspectorUI/.eslintrc b/Source/WebInspectorUI/.eslintrc >index a8c04ddc0627ac05842fd2b00d83fd5631bda40a..d20104356d8d3a131618794f34e436ccac16934e 100644 >--- a/Source/WebInspectorUI/.eslintrc >+++ b/Source/WebInspectorUI/.eslintrc >@@ -64,6 +64,7 @@ > > // WebInspector > "AsyncTestSuite": true, >+ "Debouncer": true, > "Formatter": true, > "FormatterContentBuilder": true, > "FrontendTestHarness": true, >@@ -80,6 +81,7 @@ > "SyncTestSuite": true, > "TestHarness": true, > "TestSuite": true, >+ "Throttler": true, > > // Externals > "CodeMirror": true, >diff --git a/Source/WebInspectorUI/UserInterface/Base/Debouncer.js b/Source/WebInspectorUI/UserInterface/Base/Debouncer.js >new file mode 100644 >index 0000000000000000000000000000000000000000..d78c9d4be2eebb9da7f641883735f9f0225e1b20 >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Base/Debouncer.js >@@ -0,0 +1,116 @@ >+/* >+ * Copyright (C) 2019 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+class Debouncer >+{ >+ constructor(callback) >+ { >+ console.assert(typeof callback === "function"); >+ >+ this._callback = callback; >+ this._lastArguments = []; >+ >+ this._timeoutIdentifier = undefined; >+ this._animationFrameIdentifier = undefined; >+ this._promiseIdentifier = undefined; >+ } >+ >+ // Public >+ >+ force() >+ { >+ this._lastArguments = arguments; >+ this._execute(); >+ } >+ >+ delayForTime(time, ...args) >+ { >+ console.assert(time >= 0); >+ >+ this.cancel(); >+ >+ this._lastArguments = args; >+ >+ this._timeoutIdentifier = setTimeout(() => { >+ this._execute(); >+ }, time); >+ } >+ >+ delayForFrame() >+ { >+ this.cancel(); >+ >+ this._lastArguments = arguments; >+ >+ this._animationFrameIdentifier = requestAnimationFrame(() => { >+ this._execute(); >+ }); >+ } >+ >+ delayForMicrotask() >+ { >+ this.cancel(); >+ >+ this._lastArguments = arguments; >+ >+ let promiseIdentifier = Symbol("next-microtask"); >+ >+ this._promiseIdentifier = promiseIdentifier; >+ >+ queueMicrotask(() => { >+ if (this._promiseIdentifier === promiseIdentifier) >+ this._execute(); >+ }); >+ } >+ >+ cancel() >+ { >+ this._lastArguments = []; >+ >+ if (this._timeoutIdentifier) { >+ clearTimeout(this._timeoutIdentifier); >+ this._timeoutIdentifier = undefined; >+ } >+ >+ if (this._animationFrameIdentifier) { >+ cancelAnimationFrame(this._animationFrameIdentifier); >+ this._animationFrameIdentifier = undefined; >+ } >+ >+ if (this._promiseIdentifier) >+ this._promiseIdentifier = undefined; >+ } >+ >+ // Private >+ >+ _execute() >+ { >+ let args = this._lastArguments; >+ >+ this.cancel(); >+ >+ this._callback.apply(undefined, args); >+ } >+} >diff --git a/Source/WebInspectorUI/UserInterface/Base/Throttler.js b/Source/WebInspectorUI/UserInterface/Base/Throttler.js >new file mode 100644 >index 0000000000000000000000000000000000000000..e300f729c303140d7b5e0c7fbbd4e9cdebd62a30 >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Base/Throttler.js >@@ -0,0 +1,97 @@ >+/* >+ * Copyright (C) 2019 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+class Throttler >+{ >+ constructor(callback, delay) >+ { >+ console.assert(typeof callback === "function"); >+ console.assert(delay >= 0); >+ >+ this._callback = callback; >+ this._delay = delay; >+ >+ this._lastArguments = []; >+ >+ this._timeoutIdentifier = undefined; >+ this._lastFireTime = NaN; >+ } >+ >+ // Public >+ >+ force() >+ { >+ this._lastArguments = arguments; >+ this._execute(); >+ } >+ >+ fire() >+ { >+ this.cancel(); >+ >+ this._lastArguments = arguments; >+ >+ if (isNaN(this._lastFireTime)) { >+ this._execute(); >+ return; >+ } >+ >+ let remaining = this._delay - (Date.now() - this._lastFireTime); >+ if (remaining <= 0) { >+ this._execute(); >+ return; >+ } >+ >+ if (this._timeoutIdentifier) >+ return; >+ >+ this._timeoutIdentifier = setTimeout(() => { >+ this._execute(); >+ }, remaining); >+ } >+ >+ cancel() >+ { >+ this._lastArguments = []; >+ >+ if (this._timeoutIdentifier) { >+ clearTimeout(this._timeoutIdentifier); >+ this._timeoutIdentifier = undefined; >+ } >+ } >+ >+ // Private >+ >+ _execute() >+ { >+ this._lastFireTime = Date.now(); >+ >+ let args = this._lastArguments; >+ >+ this.cancel(); >+ >+ this._callback.apply(undefined, args); >+ } >+} >diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >index 93a4f9ea904b6a9b0cd3e55a7c3e71b8ef9059b6..1faeba935d121ad09da637cc61ae747ccbfe079e 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/Utilities.js >+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >@@ -1480,155 +1480,6 @@ Object.defineProperty(Promise, "delay", > } > }); > >-(function() { >- // The `debounce` function lets you call any function on an object with a delay >- // and if the function keeps getting called, the delay gets reset. Since `debounce` >- // returns a Proxy, you can cache it and call multiple functions with the same delay. >- >- // Use: object.debounce(200).foo("Argument 1", "Argument 2") >- // Note: The last call's arguments get used for the delayed call. >- >- const debounceTimeoutSymbol = Symbol("debounce-timeout"); >- const debounceSoonProxySymbol = Symbol("debounce-soon-proxy"); >- >- Object.defineProperty(Object.prototype, "soon", >- { >- get() >- { >- if (!this[debounceSoonProxySymbol]) >- this[debounceSoonProxySymbol] = this.debounce(0); >- return this[debounceSoonProxySymbol]; >- } >- }); >- >- Object.defineProperty(Object.prototype, "debounce", >- { >- value(delay) >- { >- console.assert(delay >= 0); >- >- return new Proxy(this, { >- get(target, property, receiver) { >- return (...args) => { >- let original = target[property]; >- console.assert(typeof original === "function"); >- >- if (original[debounceTimeoutSymbol]) >- clearTimeout(original[debounceTimeoutSymbol]); >- >- let performWork = () => { >- original[debounceTimeoutSymbol] = undefined; >- original.apply(target, args); >- }; >- >- original[debounceTimeoutSymbol] = setTimeout(performWork, delay); >- }; >- } >- }); >- } >- }); >- >- Object.defineProperty(Function.prototype, "cancelDebounce", >- { >- value() >- { >- if (!this[debounceTimeoutSymbol]) >- return; >- >- clearTimeout(this[debounceTimeoutSymbol]); >- this[debounceTimeoutSymbol] = undefined; >- } >- }); >- >- const requestAnimationFrameSymbol = Symbol("peform-on-animation-frame"); >- const requestAnimationFrameProxySymbol = Symbol("perform-on-animation-frame-proxy"); >- >- Object.defineProperty(Object.prototype, "onNextFrame", >- { >- get() >- { >- if (!this[requestAnimationFrameProxySymbol]) { >- this[requestAnimationFrameProxySymbol] = new Proxy(this, { >- get(target, property, receiver) { >- return (...args) => { >- let original = target[property]; >- console.assert(typeof original === "function"); >- >- if (original[requestAnimationFrameSymbol]) >- return; >- >- let performWork = () => { >- original[requestAnimationFrameSymbol] = undefined; >- original.apply(target, args); >- }; >- >- original[requestAnimationFrameSymbol] = requestAnimationFrame(performWork); >- }; >- } >- }); >- } >- >- return this[requestAnimationFrameProxySymbol]; >- } >- }); >- >- const throttleTimeoutSymbol = Symbol("throttle-timeout"); >- >- Object.defineProperty(Object.prototype, "throttle", >- { >- value(delay) >- { >- console.assert(delay >= 0); >- >- let lastFireTime = NaN; >- let mostRecentArguments = null; >- >- return new Proxy(this, { >- get(target, property, receiver) { >- return (...args) => { >- let original = target[property]; >- console.assert(typeof original === "function"); >- mostRecentArguments = args; >- >- function performWork() { >- lastFireTime = Date.now(); >- original[throttleTimeoutSymbol] = undefined; >- original.apply(target, mostRecentArguments); >- } >- >- if (isNaN(lastFireTime)) { >- performWork(); >- return; >- } >- >- let remaining = delay - (Date.now() - lastFireTime); >- if (remaining <= 0) { >- original.cancelThrottle(); >- performWork(); >- return; >- } >- >- if (!original[throttleTimeoutSymbol]) >- original[throttleTimeoutSymbol] = setTimeout(performWork, remaining); >- }; >- } >- }); >- } >- }); >- >- Object.defineProperty(Function.prototype, "cancelThrottle", >- { >- value() >- { >- if (!this[throttleTimeoutSymbol]) >- return; >- >- clearTimeout(this[throttleTimeoutSymbol]); >- this[throttleTimeoutSymbol] = undefined; >- } >- }); >-})(); >- > function appendWebInspectorSourceURL(string) > { > if (string.includes("//# sourceURL")) >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index 40ddf8395158d5df9546ca4befd877868f15313b..330bffea0f74fc7afe7f60135f77f4e0ab196131 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -275,11 +275,13 @@ > > <script src="Base/WebInspector.js"></script> > <script src="Base/Platform.js"></script> >+ <script src="Base/Debouncer.js"></script> > <script src="Base/DebuggableType.js"></script> > <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> > <script src="Base/Object.js"></script> >+ <script src="Base/Throttler.js"></script> > > <script src="Base/DOMUtilities.js"></script> > <script src="Base/EventListener.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Test.html b/Source/WebInspectorUI/UserInterface/Test.html >index 06a0d84a8a330472ae04c7529fd92babd97737f5..33614474ca8316ca5fd0d08b2a57d94e358563c0 100644 >--- a/Source/WebInspectorUI/UserInterface/Test.html >+++ b/Source/WebInspectorUI/UserInterface/Test.html >@@ -37,11 +37,13 @@ > > <script src="Base/WebInspector.js"></script> > <script src="Base/Platform.js"></script> >+ <script src="Base/Debouncer.js"></script> > <script src="Base/DebuggableType.js"></script> > <script src="Base/IndexSet.js"></script> > <script src="Base/LinkedList.js"></script> > <script src="Base/ListMultimap.js"></script> > <script src="Base/Object.js"></script> >+ <script src="Base/Throttler.js"></script> > > <script src="Test/TestHarness.js"></script> > <script src="Test/FrontendTestHarness.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js b/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js >index 1322859a60816fbaf200b3113b271c6e849d9828..395a15bdcd597f46486b9934f48fae5291f0b5ec 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js >+++ b/Source/WebInspectorUI/UserInterface/Views/BezierEditor.js >@@ -101,7 +101,7 @@ WI.BezierEditor = class BezierEditor extends WI.Object > if (!isNaN(max)) > this[key].max = max; > >- this[key].addEventListener("input", this.debounce(250)._handleNumberInputInput); >+ this[key].addEventListener("input", this._handleNumberInputInput.bind(this)); > this[key].addEventListener("keydown", this._handleNumberInputKeydown.bind(this)); > } > >diff --git a/Source/WebInspectorUI/UserInterface/Views/ContentBrowser.js b/Source/WebInspectorUI/UserInterface/Views/ContentBrowser.js >index 35e7b5652e66a05da4b2f7bc7b14983305c4e3ac..6a0e37b318767ff0bca8b09562daaf8f22f38a5a 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/ContentBrowser.js >+++ b/Source/WebInspectorUI/UserInterface/Views/ContentBrowser.js >@@ -91,6 +91,10 @@ WI.ContentBrowser = class ContentBrowser extends WI.View > this._delegate = delegate || null; > > this._currentContentViewNavigationItems = []; >+ >+ this._dispatchCurrentRepresentedObjectsDidChange = new Debouncer(() => { >+ this.dispatchEventToListeners(WI.ContentBrowser.Event.CurrentRepresentedObjectsDidChange); >+ }); > } > > // Public >@@ -469,13 +473,6 @@ WI.ContentBrowser = class ContentBrowser extends WI.View > } > } > >- _dispatchCurrentRepresentedObjectsDidChangeEvent() >- { >- this._dispatchCurrentRepresentedObjectsDidChangeEvent.cancelDebounce(); >- >- this.dispatchEventToListeners(WI.ContentBrowser.Event.CurrentRepresentedObjectsDidChange); >- } >- > _contentViewSelectionPathComponentDidChange(event) > { > if (event.target !== this.currentContentView) >@@ -492,7 +489,7 @@ WI.ContentBrowser = class ContentBrowser extends WI.View > > this._navigationBar.needsLayout(); > >- this.soon._dispatchCurrentRepresentedObjectsDidChangeEvent(); >+ this._dispatchCurrentRepresentedObjectsDidChange.delayForTime(0); > } > > _contentViewSupplementalRepresentedObjectsDidChange(event) >@@ -504,7 +501,7 @@ WI.ContentBrowser = class ContentBrowser extends WI.View > if (event.target.parentContainer !== this._contentViewContainer) > return; > >- this.soon._dispatchCurrentRepresentedObjectsDidChangeEvent(); >+ this._dispatchCurrentRepresentedObjectsDidChange.delayForTime(0); > } > > _currentContentViewDidChange(event) >@@ -522,7 +519,7 @@ WI.ContentBrowser = class ContentBrowser extends WI.View > > this.dispatchEventToListeners(WI.ContentBrowser.Event.CurrentContentViewDidChange); > >- this._dispatchCurrentRepresentedObjectsDidChangeEvent(); >+ this._dispatchCurrentRepresentedObjectsDidChange.force(); > } > > _contentViewNavigationItemsDidChange(event) >diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js >index a5460266e35d281e368161baee628eff2cf19f15..c2455a663d59420b55e5374a2c5f13ccd0e2a6fe 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js >+++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeUpdater.js >@@ -48,6 +48,10 @@ WI.DOMTreeUpdater = function(treeOutline) > > // Dummy "attribute" that is used to track textContent changes. > this._textContentAttributeSymbol = Symbol("text-content-attribute"); >+ >+ this._updateModifiedNodesDebouncer = new Debouncer(() => { >+ this._updateModifiedNodes(); >+ }); > }; > > WI.DOMTreeUpdater.prototype = { >@@ -81,21 +85,21 @@ WI.DOMTreeUpdater.prototype = { > this._recentlyModifiedNodes.add(node); > > if (this._treeOutline._visible) >- this.onNextFrame._updateModifiedNodes(); >+ this._updateModifiedNodesDebouncer.delayForFrame(); > }, > > _nodeInserted: function(event) > { > this._recentlyInsertedNodes.set(event.data.node, {parent: event.data.parent}); > if (this._treeOutline._visible) >- this.onNextFrame._updateModifiedNodes(); >+ this._updateModifiedNodesDebouncer.delayForFrame(); > }, > > _nodeRemoved: function(event) > { > this._recentlyDeletedNodes.set(event.data.node, {parent: event.data.parent}); > if (this._treeOutline._visible) >- this.onNextFrame._updateModifiedNodes(); >+ this._updateModifiedNodesDebouncer.delayForFrame(); > }, > > _childNodeCountUpdated: function(event) >diff --git a/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js >index c53df4ae3a637b445c7ba1c6003094f379f3204c..139e7f62b94b729dff6083fd250f2abc2cd40718 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js >@@ -31,7 +31,14 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > > this.element.classList.add("navigation"); > >- this.contentView.element.addEventListener("scroll", this.soon._updateContentOverflowShadowVisibility); >+ this._updateContentOverflowShadowVisibilityDebouncer = new Debouncer(() => { >+ this._updateContentOverflowShadowVisibility(); >+ }); >+ this._boundUpdateContentOverflowShadowVisibilitySoon = (event) => { >+ this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); >+ }; >+ >+ this.contentView.element.addEventListener("scroll", this._boundUpdateContentOverflowShadowVisibilitySoon); > > this._contentTreeOutlineGroup = new WI.TreeOutlineGroup; > this._contentTreeOutline = this.createContentTreeOutline(); >@@ -49,8 +56,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > this._topOverflowShadowElement.classList.add(WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName, "top"); > } > >- this._boundUpdateContentOverflowShadowVisibility = this.soon._updateContentOverflowShadowVisibility; >- window.addEventListener("resize", this._boundUpdateContentOverflowShadowVisibility); >+ window.addEventListener("resize", this._boundUpdateContentOverflowShadowVisibilitySoon); > > this._filtersSetting = new WI.Setting(identifier + "-navigation-sidebar-filters", {}); > this._filterBar.filters = this._filtersSetting.value; >@@ -77,7 +83,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > > closed() > { >- window.removeEventListener("resize", this._boundUpdateContentOverflowShadowVisibility); >+ window.removeEventListener("resize", this._boundUpdateContentOverflowShadowVisibilitySoon); > WI.Frame.removeEventListener(null, null, this); > } > >@@ -273,7 +279,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > let emptyContentPlaceholderParentElement = treeOutline.element.parentNode; > emptyContentPlaceholderParentElement.appendChild(emptyContentPlaceholderElement); > >- this._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.force(); > > return emptyContentPlaceholderElement; > } >@@ -289,7 +295,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > emptyContentPlaceholderElement.remove(); > this._emptyContentPlaceholderElements.delete(treeOutline); > >- this._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.force(); > } > > updateEmptyContentPlaceholder(message, treeOutline) >@@ -345,7 +351,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > } > > this._checkForEmptyFilterResults(); >- this._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.force(); > } > > resetFilter() >@@ -468,7 +474,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > { > super.shown(); > >- this._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.force(); > } > > // Protected >@@ -503,8 +509,6 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > if (!this.visible) > return; > >- this._updateContentOverflowShadowVisibility.cancelDebounce(); >- > let scrollHeight = this.contentView.element.scrollHeight; > let offsetHeight = this.contentView.element.offsetHeight; > >@@ -607,7 +611,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > this._checkForEmptyFilterResults(); > > if (this.visible) >- this.soon._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); > > if (this.selected) > this._checkElementsForPendingViewStateCookie([treeElement]); >@@ -615,7 +619,7 @@ WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel > > _treeElementDisclosureDidChange(event) > { >- this.soon._updateContentOverflowShadowVisibility(); >+ this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); > } > > _checkForStaleResourcesIfNeeded() >diff --git a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >index e8f7db1397eea530fd9d03582ca7b7e4114dab1a..137c9578f451240274732d18458da536c6fcd85d 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/RecordingContentView.js >@@ -35,7 +35,12 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > this._action = null; > this._snapshots = []; > this._initialContent = null; >- this._throttler = this.throttle(200); >+ this._generateContentThrottler = new Throttler(() => { >+ if (this.representedObject.type === WI.Recording.Type.Canvas2D) >+ this._generateContentCanvas2D(this._index); >+ else if (this.representedObject.type === WI.Recording.Type.CanvasBitmapRenderer || this.representedObject.type === WI.Recording.Type.CanvasWebGL) >+ this._generateContentFromSnapshot(this._index); >+ }, 200); > > this.element.classList.add("recording", this.representedObject.type); > >@@ -103,10 +108,7 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > > this._updateSliderValue(); > >- if (this.representedObject.type === WI.Recording.Type.Canvas2D) >- this._throttler._generateContentCanvas2D(index); >- else if (this.representedObject.type === WI.Recording.Type.CanvasBitmapRenderer || this.representedObject.type === WI.Recording.Type.CanvasWebGL) >- this._throttler._generateContentFromSnapshot(index); >+ this._generateContentThrottler.fire(); > > this._action = this.representedObject.actions[this._index]; > >@@ -131,8 +133,7 @@ WI.RecordingContentView = class RecordingContentView extends WI.ContentView > { > super.hidden(); > >- this._generateContentCanvas2D.cancelThrottle(); >- this._generateContentFromSnapshot.cancelThrottle(); >+ this._generateContentThrottler.cancel(); > } > > // Protected >diff --git a/Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSidebarPanel.js >index 4cf9b8ac5a99189b15b9e0fedbefb37e19f24cdc..759e5d65874b99cb2e7948799c2db2b5fae929aa 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSidebarPanel.js >@@ -263,8 +263,6 @@ WI.ResourceDetailsSidebarPanel = class ResourceDetailsSidebarPanel extends WI.De > > _refreshRelatedResourcesSection() > { >- this._refreshRelatedResourcesSection.cancelThrottle(); >- > // Hide the section if we don't have anything to show. > let groups = this._locationSection.groups; > let isSectionVisible = groups.includes(this._relatedResourcesGroup); >@@ -607,8 +605,11 @@ WI.ResourceDetailsSidebarPanel = class ResourceDetailsSidebarPanel extends WI.De > > _applyResourceEventListeners() > { >- if (!this._throttler) >- this._throttler = this.throttle(250); >+ if (!this._refreshRelatedResourcesSectionThrottler) { >+ this._refreshRelatedResourcesSectionThrottler = new Throttler(() => { >+ this._refreshRelatedResourcesSection(); >+ }, 250); >+ } > > this._resource.addEventListener(WI.Resource.Event.URLDidChange, this._refreshURL, this); > this._resource.addEventListener(WI.Resource.Event.MIMETypeDidChange, this._refreshMIMEType, this); >@@ -620,7 +621,9 @@ WI.ResourceDetailsSidebarPanel = class ResourceDetailsSidebarPanel extends WI.De > this._resource.addEventListener(WI.Resource.Event.MetricsDidChange, this._refreshRequestAndResponse, this); > this._resource.addEventListener(WI.Resource.Event.SizeDidChange, this._refreshDecodedSize, this); > this._resource.addEventListener(WI.Resource.Event.TransferSizeDidChange, this._refreshTransferSize, this); >- this._resource.addEventListener(WI.Resource.Event.InitiatedResourcesDidChange, this._throttler._refreshRelatedResourcesSection, this); >+ this._resource.addEventListener(WI.Resource.Event.InitiatedResourcesDidChange, () => { >+ this._refreshRelatedResourcesSectionThrottler.fire(); >+ }, this); > > this._needsToRemoveResourceEventListeners = true; > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/ShaderProgramContentView.js b/Source/WebInspectorUI/UserInterface/Views/ShaderProgramContentView.js >index 167969095defb7ee569249f7e7ba5d7450f2f83f..283ec49907717c85ddadbbc0e2a5dcb29aae0532 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/ShaderProgramContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/ShaderProgramContentView.js >@@ -31,6 +31,10 @@ WI.ShaderProgramContentView = class ShaderProgramContentView extends WI.ContentV > > super(shaderProgram); > >+ let contentDidChangeDebouncer = new Debouncer((event) => { >+ this._contentDidChange(event); >+ }); >+ > this.element.classList.add("shader-program"); > > let createEditor = (shaderType) => { >@@ -38,7 +42,9 @@ WI.ShaderProgramContentView = class ShaderProgramContentView extends WI.ContentV > textEditor.readOnly = false; > textEditor.addEventListener(WI.TextEditor.Event.Focused, this._editorFocused, this); > textEditor.addEventListener(WI.TextEditor.Event.NumberOfSearchResultsDidChange, this._numberOfSearchResultsDidChange, this); >- textEditor.addEventListener(WI.TextEditor.Event.ContentDidChange, this.debounce(250)._contentDidChange, this); >+ textEditor.addEventListener(WI.TextEditor.Event.ContentDidChange, (event) => { >+ contentDidChangeDebouncer.delayForTime(250, event); >+ }, this); > textEditor.element.classList.add("shader"); > > let shaderTypeContainer = textEditor.element.insertAdjacentElement("afterbegin", document.createElement("div")); >diff --git a/Source/WebInspectorUI/UserInterface/Views/SpringEditor.js b/Source/WebInspectorUI/UserInterface/Views/SpringEditor.js >index 63daf7a52cfd9b672cb692e0b1256b5560241075..d060fd5ed40f102a652f940f88f10beb3ca4e406 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/SpringEditor.js >+++ b/Source/WebInspectorUI/UserInterface/Views/SpringEditor.js >@@ -29,15 +29,19 @@ WI.SpringEditor = class SpringEditor extends WI.Object > { > super(); > >+ let boundResetPreviewAnimation = (event) => { >+ this._resetPreviewAnimation(event); >+ }; >+ > this._element = document.createElement("div"); > this._element.classList.add("spring-editor"); > > this._previewContainer = this._element.createChild("div", "spring-preview"); > this._previewContainer.title = WI.UIString("Restart animation"); >- this._previewContainer.addEventListener("mousedown", this._resetPreviewAnimation.bind(this)); >+ this._previewContainer.addEventListener("mousedown", boundResetPreviewAnimation); > > this._previewElement = this._previewContainer.createChild("div"); >- this._previewElement.addEventListener("transitionend", this.debounce(500)._resetPreviewAnimation); >+ this._previewElement.addEventListener("transitionend", boundResetPreviewAnimation); > > this._timingContainer = this._element.createChild("div", "spring-timing"); > >@@ -213,7 +217,7 @@ WI.SpringEditor = class SpringEditor extends WI.Object > if (!event) > this._timingContainer.dataset.duration = "0"; > >- this.debounce(500)._updatePreviewAnimation(event); >+ this._updatePreviewAnimation(event); > } > > _updatePreviewAnimation(event) >diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeElement.js b/Source/WebInspectorUI/UserInterface/Views/TreeElement.js >index d3b892943527766129b244affb9a969e0026a02a..eff98e8fbee7ffcd638a5b31f2f89dc61bf33a39 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TreeElement.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TreeElement.js >@@ -165,10 +165,12 @@ WI.TreeElement = class TreeElement extends WI.Object > this._childrenListNode.hidden = this._hidden; > > if (this.treeOutline) { >- let focusedTreeElement = null; >- if (!this._hidden && this.selected) >- focusedTreeElement = this; >- this.treeOutline.soon.updateVirtualizedElements(focusedTreeElement); >+ if (this.treeOutline.virtualized) { >+ let focusedTreeElement = null; >+ if (!this._hidden && this.selected) >+ focusedTreeElement = this; >+ this.treeOutline.updateVirtualizedElementsDebouncer.delayForTime(0, focusedTreeElement); >+ } > > this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementVisibilityDidChange, {element: this}); > } >@@ -203,7 +205,13 @@ WI.TreeElement = class TreeElement extends WI.Object > if (!this.treeOutline) > return; > >- this.onNextFrame._fireDidChange(); >+ if (!this._fireDidChangeDebouncer) { >+ this._fireDidChangeDebouncer = new Debouncer(() => { >+ this._fireDidChange(); >+ }); >+ } >+ >+ this._fireDidChangeDebouncer.delayForFrame(); > } > > _setListItemNodeContent() >@@ -261,8 +269,8 @@ WI.TreeElement = class TreeElement extends WI.Object > this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); > } > >- if (this.treeOutline) >- this.treeOutline.soon.updateVirtualizedElements(); >+ if (this.treeOutline && this.treeOutline.virtualized) >+ this.treeOutline.updateVirtualizedElementsDebouncer.delayForTime(0); > > if (this.selected) > this.select(); >@@ -279,8 +287,8 @@ WI.TreeElement = class TreeElement extends WI.Object > if (this._childrenListNode && this._childrenListNode.parentNode) > this._childrenListNode.parentNode.removeChild(this._childrenListNode); > >- if (this.treeOutline) >- this.treeOutline.soon.updateVirtualizedElements(); >+ if (this.treeOutline && this.treeOutline.virtualized) >+ this.treeOutline.updateVirtualizedElementsDebouncer.delayForTime(0); > } > > static treeElementToggled(event) >@@ -346,8 +354,8 @@ WI.TreeElement = class TreeElement extends WI.Object > if (this.oncollapse) > this.oncollapse(this); > >- if (this.treeOutline) { >- this.treeOutline.soon.updateVirtualizedElements(this); >+ if (this.treeOutline && this.treeOutline.virtualized) { >+ this.treeOutline.updateVirtualizedElementsDebouncer.delayForTime(0, this); > > this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementDisclosureDidChanged, {element: this}); > } >@@ -413,8 +421,8 @@ WI.TreeElement = class TreeElement extends WI.Object > if (this.onexpand) > this.onexpand(this); > >- if (this.treeOutline) { >- this.treeOutline.soon.updateVirtualizedElements(this); >+ if (this.treeOutline && this.treeOutline.virtualized) { >+ this.treeOutline.updateVirtualizedElementsDebouncer.delayForTime(0, this); > > this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementDisclosureDidChanged, {element: this}); > } >@@ -466,8 +474,8 @@ WI.TreeElement = class TreeElement extends WI.Object > > // This must be called before onreveal, as some subclasses will scrollIntoViewIfNeeded and > // we should update the visible elements before attempting to scroll. >- if (this.treeOutline) >- this.treeOutline.updateVirtualizedElements(this); >+ if (this.treeOutline && this.treeOutline.virtualized) >+ this.treeOutline.updateVirtualizedElementsDebouncer.force(this); > > if (this.onreveal) > this.onreveal(this); >diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >index 9f21a2c55a78e12673bf49d9570ec5a0485f6a9b..fe57c785ec2f67bf83417f3f9cb1b11981c3222f 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >+++ b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js >@@ -62,6 +62,7 @@ WI.TreeOutline = class TreeOutline extends WI.Object > this._processingSelectionChange = false; > this._suppressNextSelectionDidChangeEvent = false; > >+ this._virtualizedDebouncer = null; > this._virtualizedVisibleTreeElements = null; > this._virtualizedAttachedTreeElements = null; > this._virtualizedScrollContainer = null; >@@ -688,6 +689,11 @@ WI.TreeOutline = class TreeOutline extends WI.Object > { > console.assert(!isNaN(treeItemHeight)); > >+ let boundUpdateVirtualizedElements = (focusedTreeElement) => { >+ this._updateVirtualizedElements(focusedTreeElement); >+ }; >+ >+ this._virtualizedDebouncer = new Debouncer(boundUpdateVirtualizedElements); > this._virtualizedVisibleTreeElements = new Set; > this._virtualizedAttachedTreeElements = new Set; > this._virtualizedScrollContainer = scrollContainer; >@@ -695,104 +701,15 @@ WI.TreeOutline = class TreeOutline extends WI.Object > this._virtualizedTopSpacer = document.createElement("div"); > this._virtualizedBottomSpacer = document.createElement("div"); > >- let throttler = this.throttle(1000 / 16); >+ let throttler = new Throttler(boundUpdateVirtualizedElements, 1000 / 16); > this._virtualizedScrollContainer.addEventListener("scroll", (event) => { >- throttler.updateVirtualizedElements(); >+ throttler.fire(); > }); > } > >- updateVirtualizedElements(focusedTreeElement) >+ get updateVirtualizedElementsDebouncer() > { >- if (!this.virtualized) >- return; >- >- function walk(parent, callback, count = 0) { >- let shouldReturn = false; >- for (let child of parent.children) { >- if (!child.revealed(false)) >- continue; >- >- shouldReturn = callback(child, count); >- if (shouldReturn) >- break; >- >- ++count; >- if (child.expanded) { >- let result = walk(child, callback, count); >- count = result.count; >- if (result.shouldReturn) >- break; >- } >- } >- return {count, shouldReturn}; >- } >- >- let {numberVisible, extraRows, firstItem, lastItem} = this._calculateVirtualizedValues(); >- >- let shouldScroll = false; >- if (focusedTreeElement && focusedTreeElement.revealed(false)) { >- let index = walk(this, (treeElement) => treeElement === focusedTreeElement).count; >- if (index < firstItem) { >- firstItem = index - extraRows; >- lastItem = index + numberVisible + extraRows; >- } else if (index > lastItem) { >- firstItem = index - numberVisible - extraRows; >- lastItem = index + extraRows; >- } >- >- // Only scroll if the `focusedTreeElement` is outside the visible items, not including >- // the added buffer `extraRows`. >- shouldScroll = (index < firstItem + extraRows) || (index > lastItem - extraRows); >- } >- >- console.assert(firstItem < lastItem); >- >- let visibleTreeElements = new Set; >- let treeElementsToAttach = new Set; >- let treeElementsToDetach = new Set; >- let totalItems = walk(this, (treeElement, count) => { >- if (count >= firstItem && count <= lastItem) { >- treeElementsToAttach.add(treeElement); >- if (count >= firstItem + extraRows && count <= lastItem - extraRows) >- visibleTreeElements.add(treeElement); >- } else if (treeElement.element.parentNode) >- treeElementsToDetach.add(treeElement); >- >- return false; >- }).count; >- >- // Redraw if we are about to scroll. >- if (!shouldScroll) { >- // Redraw if all of the previously centered `WI.TreeElement` are no longer centered. >- if (visibleTreeElements.intersects(this._virtualizedVisibleTreeElements)) { >- // Redraw if there is a `WI.TreeElement` that should be shown that isn't attached. >- if (visibleTreeElements.isSubsetOf(this._virtualizedAttachedTreeElements)) >- return; >- } >- } >- >- this._virtualizedVisibleTreeElements = visibleTreeElements; >- this._virtualizedAttachedTreeElements = treeElementsToAttach; >- >- for (let treeElement of treeElementsToDetach) >- treeElement.element.remove(); >- >- for (let treeElement of treeElementsToAttach) { >- treeElement.parent._childrenListNode.appendChild(treeElement.element); >- if (treeElement._childrenListNode) >- treeElement.parent._childrenListNode.appendChild(treeElement._childrenListNode); >- } >- >- this._virtualizedTopSpacer.style.height = (Math.max(firstItem, 0) * this._virtualizedTreeItemHeight) + "px"; >- if (this.element.previousElementSibling !== this._virtualizedTopSpacer) >- this.element.parentNode.insertBefore(this._virtualizedTopSpacer, this.element); >- >- this._virtualizedBottomSpacer.style.height = (Math.max(totalItems - lastItem, 0) * this._virtualizedTreeItemHeight) + "px"; >- if (this.element.nextElementSibling !== this._virtualizedBottomSpacer) >- this.element.parentNode.insertBefore(this._virtualizedBottomSpacer, this.element.nextElementSibling); >- >- if (shouldScroll) >- this._virtualizedScrollContainer.scrollTop = (firstItem + extraRows) * this._virtualizedTreeItemHeight; >+ return this._virtualizedDebouncer; > } > > // SelectionController delegate >@@ -990,6 +907,99 @@ WI.TreeOutline = class TreeOutline extends WI.Object > }; > } > >+ _updateVirtualizedElements(focusedTreeElement) >+ { >+ console.assert(this.virtualized); >+ >+ function walk(parent, callback, count = 0) { >+ let shouldReturn = false; >+ for (let child of parent.children) { >+ if (!child.revealed(false)) >+ continue; >+ >+ shouldReturn = callback(child, count); >+ if (shouldReturn) >+ break; >+ >+ ++count; >+ if (child.expanded) { >+ let result = walk(child, callback, count); >+ count = result.count; >+ if (result.shouldReturn) >+ break; >+ } >+ } >+ return {count, shouldReturn}; >+ } >+ >+ let {numberVisible, extraRows, firstItem, lastItem} = this._calculateVirtualizedValues(); >+ >+ let shouldScroll = false; >+ if (focusedTreeElement && focusedTreeElement.revealed(false)) { >+ let index = walk(this, (treeElement) => treeElement === focusedTreeElement).count; >+ if (index < firstItem) { >+ firstItem = index - extraRows; >+ lastItem = index + numberVisible + extraRows; >+ } else if (index > lastItem) { >+ firstItem = index - numberVisible - extraRows; >+ lastItem = index + extraRows; >+ } >+ >+ // Only scroll if the `focusedTreeElement` is outside the visible items, not including >+ // the added buffer `extraRows`. >+ shouldScroll = (index < firstItem + extraRows) || (index > lastItem - extraRows); >+ } >+ >+ console.assert(firstItem < lastItem); >+ >+ let visibleTreeElements = new Set; >+ let treeElementsToAttach = new Set; >+ let treeElementsToDetach = new Set; >+ let totalItems = walk(this, (treeElement, count) => { >+ if (count >= firstItem && count <= lastItem) { >+ treeElementsToAttach.add(treeElement); >+ if (count >= firstItem + extraRows && count <= lastItem - extraRows) >+ visibleTreeElements.add(treeElement); >+ } else if (treeElement.element.parentNode) >+ treeElementsToDetach.add(treeElement); >+ >+ return false; >+ }).count; >+ >+ // Redraw if we are about to scroll. >+ if (!shouldScroll) { >+ // Redraw if all of the previously centered `WI.TreeElement` are no longer centered. >+ if (visibleTreeElements.intersects(this._virtualizedVisibleTreeElements)) { >+ // Redraw if there is a `WI.TreeElement` that should be shown that isn't attached. >+ if (visibleTreeElements.isSubsetOf(this._virtualizedAttachedTreeElements)) >+ return; >+ } >+ } >+ >+ this._virtualizedVisibleTreeElements = visibleTreeElements; >+ this._virtualizedAttachedTreeElements = treeElementsToAttach; >+ >+ for (let treeElement of treeElementsToDetach) >+ treeElement.element.remove(); >+ >+ for (let treeElement of treeElementsToAttach) { >+ treeElement.parent._childrenListNode.appendChild(treeElement.element); >+ if (treeElement._childrenListNode) >+ treeElement.parent._childrenListNode.appendChild(treeElement._childrenListNode); >+ } >+ >+ this._virtualizedTopSpacer.style.height = (Math.max(firstItem, 0) * this._virtualizedTreeItemHeight) + "px"; >+ if (this.element.previousElementSibling !== this._virtualizedTopSpacer) >+ this.element.parentNode.insertBefore(this._virtualizedTopSpacer, this.element); >+ >+ this._virtualizedBottomSpacer.style.height = (Math.max(totalItems - lastItem, 0) * this._virtualizedTreeItemHeight) + "px"; >+ if (this.element.nextElementSibling !== this._virtualizedBottomSpacer) >+ this.element.parentNode.insertBefore(this._virtualizedBottomSpacer, this.element.nextElementSibling); >+ >+ if (shouldScroll) >+ this._virtualizedScrollContainer.scrollTop = (firstItem + extraRows) * this._virtualizedTreeItemHeight; >+ } >+ > _handleContextmenu(event) > { > let treeElement = this.treeElementFromEvent(event); >diff --git a/Source/WebInspectorUI/UserInterface/Views/WebSocketContentView.js b/Source/WebInspectorUI/UserInterface/Views/WebSocketContentView.js >index 3bf0d6463c5af77c830c4cac9100b2c1a5cac8a1..e47612397fee4e1cee84eb760ef98f32145903bc 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/WebSocketContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/WebSocketContentView.js >@@ -31,6 +31,10 @@ WI.WebSocketContentView = class WebSocketContentView extends WI.ContentView > > super(resource); > >+ this._updateFramesDebouncer = new Debouncer(() => { >+ this._updateFrames(); >+ }); >+ > this._resource = resource; > this._framesRendered = 0; > this._lastRenderedReadyState = null; >@@ -81,7 +85,7 @@ WI.WebSocketContentView = class WebSocketContentView extends WI.ContentView > > shown() > { >- this._updateFrames(); >+ this._updateFramesDebouncer.force(); > this._resource.addEventListener(WI.WebSocketResource.Event.FrameAdded, this._updateFramesSoon, this); > this._resource.addEventListener(WI.WebSocketResource.Event.ReadyStateChanged, this._updateFramesSoon, this); > } >@@ -96,7 +100,7 @@ WI.WebSocketContentView = class WebSocketContentView extends WI.ContentView > > _updateFramesSoon() > { >- this.onNextFrame._updateFrames(); >+ this._updateFramesDebouncer.delayForFrame(); > } > > _updateFrames() >@@ -119,8 +123,15 @@ WI.WebSocketContentView = class WebSocketContentView extends WI.ContentView > this._lastRenderedReadyState = this._resource.readyState; > } > >- if (shouldScrollToBottom) >- this._dataGrid.onNextFrame.scrollToLastRow(); >+ if (shouldScrollToBottom) { >+ if (!this._scrollToLastRowDebouncer) { >+ this._scrollToLastRowDebouncer = new Debouncer(() => { >+ this._dataGrid.scrollToLastRow(); >+ }); >+ } >+ >+ this._scrollToLastRowDebouncer.delayForFrame(); >+ } > } > > _addFrame(data, isOutgoing, opcode, time) >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index e390e49f8e83b5b2ff2f011af03523c0d9fc0d81..ddf48553b463c4cf147a7cec4bc9cde30ab619b6 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,15 @@ >+2019-02-15 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: make debounce Proxy into its own class >+ https://bugs.webkit.org/show_bug.cgi?id=194721 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * inspector/unit-tests/debounce.html: >+ * inspector/unit-tests/debounce-expected.txt: >+ * inspector/unit-tests/throttle.html: >+ * inspector/unit-tests/throttle-expected.txt: >+ > 2019-02-15 Per Arne Vollan <pvollan@apple.com> > > [WebVTT] Inline WebVTT styles should start with '::cue' >diff --git a/LayoutTests/inspector/unit-tests/debounce-expected.txt b/LayoutTests/inspector/unit-tests/debounce-expected.txt >index 60098053236ba7c42a2cd9959170fb53dd81e493..38c62bb95aa9aef76e26b4a8cc0bb6a49364392c 100644 >--- a/LayoutTests/inspector/unit-tests/debounce-expected.txt >+++ b/LayoutTests/inspector/unit-tests/debounce-expected.txt >@@ -1,11 +1,10 @@ >-Testing debounce proxy support. >+Testing debounce functionality. > > > == Running test suite: Debounce > -- Running test case: Basic Debounce > PASS: Debounced successfully. > PASS: Call delayed at least 10ms. >-PASS: `this` is the right object. > PASS: Arguments length is 2. > PASS: First argument is 4. > PASS: Second argument is 'abc'. >@@ -13,14 +12,12 @@ PASS: Second argument is 'abc'. > -- Running test case: Multiple Debounce Delays > PASS: Debounced successfully. > PASS: Call delayed at least 40ms. >-PASS: `this` is the right object. > PASS: Arguments length is 2. > PASS: First argument is 4. > PASS: Second argument is 'abc'. > > -- Running test case: Soon Debounce > PASS: Debounced successfully. >-PASS: `this` is the right object. > PASS: Arguments length is 2. > PASS: First argument is 4. > PASS: Second argument is 'abc'. >diff --git a/LayoutTests/inspector/unit-tests/debounce.html b/LayoutTests/inspector/unit-tests/debounce.html >index a4f00718ebf548d308b877235b727a22061e877a..a6fe8d79fa3b0c9a1c7c1904f547c0330f23aa4f 100644 >--- a/LayoutTests/inspector/unit-tests/debounce.html >+++ b/LayoutTests/inspector/unit-tests/debounce.html >@@ -1,4 +1,4 @@ >-<!doctype html> >+<!DOCTYPE html> > <html> > <head> > <script src="../../http/tests/inspector/resources/inspector-test.js"></script> >@@ -13,29 +13,25 @@ function test() > let callCount = 0; > let startTime = performance.now(); > >- let object = { >- test(foo, bar) { >- if (++callCount === 1) >- InspectorTest.pass("Debounced successfully."); >- else >- InspectorTest.fail("Debounced function called multiple times."); >+ let debouncer = new Debouncer((foo, bar) => { >+ if (++callCount === 1) >+ InspectorTest.pass("Debounced successfully."); >+ else >+ InspectorTest.fail("Debounced function called multiple times."); > >- InspectorTest.expectThat(performance.now() - startTime >= 10, "Call delayed at least 10ms."); >+ InspectorTest.expectThat(performance.now() - startTime >= 10, "Call delayed at least 10ms."); > >- InspectorTest.expectThat(this === object, "`this` is the right object."); >- InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >- InspectorTest.expectThat(foo === 4, "First argument is 4."); >- InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); >+ InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >+ InspectorTest.expectThat(foo === 4, "First argument is 4."); >+ InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); > >- resolve(); >- } >- }; >+ resolve(); >+ }); > >- let debounceProxy = object.debounce(10); >- debounceProxy.test(1, 'xyz'); >- debounceProxy.test(2, 'fgh'); >- debounceProxy.test(3, 'ert'); >- debounceProxy.test(4, 'abc'); >+ debouncer.delayForTime(10, 1, 'xyz'); >+ debouncer.delayForTime(10, 2, 'fgh'); >+ debouncer.delayForTime(10, 3, 'ert'); >+ debouncer.delayForTime(10, 4, 'abc'); > > if (!callCount) > return; >@@ -51,28 +47,25 @@ function test() > let callCount = 0; > let startTime = performance.now(); > >- let object = { >- test(foo, bar) { >- if (++callCount === 1) >- InspectorTest.pass("Debounced successfully."); >- else >- InspectorTest.fail("Debounced function called multiple times."); >+ let debouncer = new Debouncer((foo, bar) => { >+ if (++callCount === 1) >+ InspectorTest.pass("Debounced successfully."); >+ else >+ InspectorTest.fail("Debounced function called multiple times."); > >- InspectorTest.expectThat(performance.now() - startTime >= 40, "Call delayed at least 40ms."); >+ InspectorTest.expectThat(performance.now() - startTime >= 40, "Call delayed at least 40ms."); > >- InspectorTest.expectThat(this === object, "`this` is the right object."); >- InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >- InspectorTest.expectThat(foo === 4, "First argument is 4."); >- InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); >+ InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >+ InspectorTest.expectThat(foo === 4, "First argument is 4."); >+ InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); > >- resolve(); >- } >- }; >+ resolve(); >+ }); > >- object.debounce(10).test(1, 'xyz'); >- object.debounce(20).test(2, 'fgh'); >- object.debounce(30).test(3, 'ert'); >- object.debounce(40).test(4, 'abc'); >+ debouncer.delayForTime(10, 1, 'xyz'); >+ debouncer.delayForTime(20, 2, 'fgh'); >+ debouncer.delayForTime(30, 3, 'ert'); >+ debouncer.delayForTime(40, 4, 'abc'); > > if (!callCount) > return; >@@ -87,26 +80,23 @@ function test() > test(resolve, reject) { > let callCount = 0; > >- let object = { >- test(foo, bar) { >- if (++callCount === 1) >- InspectorTest.pass("Debounced successfully."); >- else >- InspectorTest.fail("Debounced function called multiple times."); >+ let debouncer = new Debouncer((foo, bar) => { >+ if (++callCount === 1) >+ InspectorTest.pass("Debounced successfully."); >+ else >+ InspectorTest.fail("Debounced function called multiple times."); > >- InspectorTest.expectThat(this === object, "`this` is the right object."); >- InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >- InspectorTest.expectThat(foo === 4, "First argument is 4."); >- InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); >+ InspectorTest.expectThat(arguments.length === 2, "Arguments length is 2."); >+ InspectorTest.expectThat(foo === 4, "First argument is 4."); >+ InspectorTest.expectThat(bar === "abc", "Second argument is 'abc'."); > >- resolve(); >- } >- }; >+ resolve(); >+ }); > >- object.soon.test(1, 'xyz'); >- object.soon.test(2, 'fgh'); >- object.soon.test(3, 'ert'); >- object.soon.test(4, 'abc'); >+ debouncer.delayForTime(0, 1, 'xyz'); >+ debouncer.delayForTime(0, 2, 'fgh'); >+ debouncer.delayForTime(0, 3, 'ert'); >+ debouncer.delayForTime(0, 4, 'abc'); > > if (!callCount) > return; >@@ -119,17 +109,18 @@ function test() > suite.addTestCase({ > name: "Cancel Debounce", > test(resolve, reject) { >- let object = { >- test() { >- InspectorTest.fail("Debounced function call should have been canceled."); >- resolve(); >- } >- }; >+ let debouncer = new Debouncer(() => { >+ InspectorTest.fail("Debounced function call should have been canceled."); >+ resolve(); >+ }); > >- object.debounce(10).test(); >- object.test.cancelDebounce(); >+ debouncer.delayForTime(10); >+ debouncer.cancel(); > >- setTimeout(() => { InspectorTest.pass("Debounced function canceled."); resolve(); }, 20); >+ setTimeout(() => { >+ InspectorTest.pass("Debounced function canceled."); >+ resolve(); >+ }, 20); > } > }); > >@@ -138,6 +129,6 @@ function test() > </script> > </head> > <body onload="runTest()"> >- <p>Testing debounce proxy support.</p> >+ <p>Testing debounce functionality.</p> > </body> > </html> >diff --git a/LayoutTests/inspector/unit-tests/throttle-expected.txt b/LayoutTests/inspector/unit-tests/throttle-expected.txt >index ba821c9313641fdcabff35bdc0f5f992020b4b85..58eaff7e7a06b1c3fd0b3736b5454ff77c555e3a 100644 >--- a/LayoutTests/inspector/unit-tests/throttle-expected.txt >+++ b/LayoutTests/inspector/unit-tests/throttle-expected.txt >@@ -1,4 +1,4 @@ >-Test throttle proxy support. >+Test throttle functionality. > > > == Running test suite: Throttle >@@ -6,9 +6,6 @@ Test throttle proxy support. > Call throttled function every 1 ms for 110 ms. > PASS: All calls delayed at least 50 ms. > >--- Running test case: Throttle proxy uniqueness >-PASS: Each call to throttle should return a new proxy. >- > -- Running test case: Throttled function arguments > PASS: Trailing call invoked with most recent arguments. > >diff --git a/LayoutTests/inspector/unit-tests/throttle.html b/LayoutTests/inspector/unit-tests/throttle.html >index f8d6d2d82c852d8a53d89e87d3aea6d4f72b71e0..193e4665c7780d98777f442c376e38f6c6ac7297 100644 >--- a/LayoutTests/inspector/unit-tests/throttle.html >+++ b/LayoutTests/inspector/unit-tests/throttle.html >@@ -1,4 +1,4 @@ >-<!doctype html> >+<!DOCTYPE html> > <html> > <head> > <script src="../../http/tests/inspector/resources/inspector-test.js"></script> >@@ -17,51 +17,37 @@ function test() > let lastCallTime = NaN; > let numberOfCalls = 0; > >- let object = { >- test(isTrailing) { >- let now = Date.now(); >+ let throttler = new Throttler((isTrailing) => { >+ let now = Date.now(); > >- if (++numberOfCalls > maximumNumberOfCalls) { >- reject("Throttled function called too many times."); >+ if (++numberOfCalls > maximumNumberOfCalls) { >+ reject("Throttled function called too many times."); >+ return; >+ } >+ >+ if (!isNaN(lastCallTime)) { >+ let actualDelay = now - lastCallTime; >+ if (actualDelay < delay) { >+ InspectorTest.fail(`Delay should be at least ${delay} ms. Was ${actualDelay} ms.`); >+ reject(); > return; > } >- >- if (!isNaN(lastCallTime)) { >- let actualDelay = now - lastCallTime; >- if (actualDelay < delay) { >- InspectorTest.fail(`Delay should be at least ${delay} ms. Was ${actualDelay} ms.`); >- reject(); >- return; >- } >- } >- >- lastCallTime = now; >- >- if (isTrailing) { >- InspectorTest.pass(`All calls delayed at least ${delay} ms.`); >- resolve(); >- } > } >- }; >+ >+ lastCallTime = now; >+ >+ if (isTrailing) { >+ InspectorTest.pass(`All calls delayed at least ${delay} ms.`); >+ resolve(); >+ } >+ }, delay); > > InspectorTest.log(`Call throttled function every 1 ms for ${duration} ms.`); > >- let throttleProxy = object.throttle(delay); >- throttleProxy.test(); >+ throttler.fire(); > > for (let i = 1; i <= duration; i++) >- setTimeout(() => { throttleProxy.test(i === duration); }, i); >- } >- }); >- >- suite.addTestCase({ >- name: "Throttle proxy uniqueness", >- test(resolve, reject) { >- let object = {}; >- let firstProxy = object.throttle(1); >- let secondProxy = object.throttle(1); >- InspectorTest.expectNotEqual(firstProxy, secondProxy, "Each call to throttle should return a new proxy."); >- resolve(); >+ setTimeout(() => { throttler.fire(i === duration); }, i); > } > }); > >@@ -71,23 +57,20 @@ function test() > let values = [1, 2, 3]; > let isLeadingCall = true; > >- let object = { >- test(value) { >- if (isLeadingCall) { >- isLeadingCall = false; >- return; >- } >- >- let expected = values.lastValue; >- InspectorTest.expectEqual(value, expected, "Trailing call invoked with most recent arguments."); >- resolve(); >+ let throttler = new Throttler((value) => { >+ if (isLeadingCall) { >+ isLeadingCall = false; >+ return; > } >- }; >+ >+ let expected = values.lastValue; >+ InspectorTest.expectEqual(value, expected, "Trailing call invoked with most recent arguments."); >+ resolve(); >+ }, 50); > > let i = 0; >- let throttleProxy = object.throttle(50); > for (let value of values) >- setTimeout(() => { throttleProxy.test(value); }, i++); >+ setTimeout(() => { throttler.fire(value); }, i++); > } > }); > >@@ -96,20 +79,17 @@ function test() > test(resolve, reject) { > let canceled = false; > >- let object = { >- test() { >- if (!canceled) >- return; >- InspectorTest.fail("Called throttled function after cancel."); >- reject(); >- } >- }; >+ let throttler = new Throttler(() => { >+ if (!canceled) >+ return; >+ InspectorTest.fail("Called throttled function after cancel."); >+ reject(); >+ }, 20); > >- let throttleProxy = object.throttle(20); >- throttleProxy.test(); >- throttleProxy.test(); >+ throttler.fire(); >+ throttler.fire(); > >- object.test.cancelThrottle(); >+ throttler.cancel(); > canceled = true; > > InspectorTest.log("Canceled throttled function."); >@@ -126,6 +106,6 @@ function test() > </script> > </head> > <body onload="runTest()"> >- <p>Test throttle proxy support.</p> >+ <p>Test throttle functionality.</p> > </body> > </html>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 194721
:
362178
|
362193
|
362220
|
362636