Skip to content

Commit a2e3ab0

Browse files
Web Inspector: Performance issue with search tab and single character
rdar://49234522 https://bugs.webkit.org/show_bug.cgi?id=262158 Reviewed by Devin Rousso. Web Inspector search works by first requesting a list of all resources that have matches for a query. Then, iterating through each resource, it gets lines of text where matches are found. Finally the same search query regex is applied to each line to get individual search result matches. Before this patch, a `WI.SearchResultTreeElement` was created and attached for each match, regardless of how many. This was expensive for a few reasons: - for each result, a few substring operations are run on the line of text to provide context around the individual match. On minified files, this means working on almost the entire contents of the file. - attaching the tree element to the tree outline creates a DOM structure for each element, without necessarily attaching it to the DOM (tree outline virtualization handles that), adds event handlers, creates relationships with sibling tree elements, etc. All this work is done regardless if the tree element will ever be seen. For queries with a very large number of results (tens or hundreds of thousands), there's a lot unnecessary work done before even showing the first few results. This work occurs on the UI thread and blocks interaction within Web Inspector. From a UX perspective, a user overwhelmed with a large volume of search results is more likely to want to refine their query than see all of the results. This patch reduces the amount of work done: - for each resource, show only the first 100 results by default. - provide UI controls per resource to load more results or all results. This allows a user to intentionally request to show a known large number of results with the implied acknowledgement that the action might cause a slowdown. This follows a pattern used elsewhere in Web Inspector: showing contents of Arrays, Sets, Maps, or DataGrids with many entries. - other piecemeal optimizations: -- event delegation for handling double-click on individual search result tree elements * Source/WebInspectorUI/UserInterface/Main.html: * Source/WebInspectorUI/UserInterface/Views/SearchResultsPlaceholderTreeElement.css: Added. (.tree-outline .item.search-results-placeholder > button): (&:first-of-type): * Source/WebInspectorUI/UserInterface/Views/SearchResultsPlaceholderTreeElement.js: Added. (WI.SearchResultsPlaceholderTreeElement): (WI.SearchResultsPlaceholderTreeElement.prototype.onattach): (WI.SearchResultsPlaceholderTreeElement.prototype._handleShowMoreButtonClicked): (WI.SearchResultsPlaceholderTreeElement.prototype._handleShowAllButtonClicked): * Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js: (WI.SearchSidebarPanel): (WI.SearchSidebarPanel.prototype.performSearch): (WI.SearchSidebarPanel.prototype.attached): (WI.SearchSidebarPanel.prototype.detached): (WI.SearchSidebarPanel.prototype.matchTreeElementAgainstCustomFilters): (WI.SearchSidebarPanel.prototype.searchResultsPlaceholderLoadMoreResults): (WI.SearchSidebarPanel.prototype._renderResultsForSourceCodeTreeElement): (WI.SearchSidebarPanel.prototype._createSearchResultsPlaceholderTreeElementIfNeeded): (WI.SearchSidebarPanel.prototype.performSearch.createTreeElementForMatchObject): Deleted. (WI.SearchSidebarPanel.prototype._treeElementDoubleClick): Deleted. * Source/WebInspectorUI/UserInterface/Views/TreeElement.js: (WI.TreeElement.prototype._setListItemNodeContent): (WI.TreeElement.prototype.get titleHTML): Deleted. (WI.TreeElement.prototype.set titleHTML): Deleted. Drive-by clean-up of unused `titleHTML` Canonical link: https://commits.webkit.org/306429@main
1 parent 872c49e commit a2e3ab0

File tree

5 files changed

+223
-65
lines changed

5 files changed

+223
-65
lines changed

Source/WebInspectorUI/UserInterface/Main.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
<link rel="stylesheet" href="Views/ScriptTimelineOverviewGraph.css">
220220
<link rel="stylesheet" href="Views/ScrubberNavigationItem.css">
221221
<link rel="stylesheet" href="Views/SearchIcons.css">
222+
<link rel="stylesheet" href="Views/SearchResultsPlaceholderTreeElement.css">
222223
<link rel="stylesheet" href="Views/SearchSidebarPanel.css">
223224
<link rel="stylesheet" href="Views/SettingEditor.css">
224225
<link rel="stylesheet" href="Views/SettingsPopover.css">
@@ -861,6 +862,7 @@
861862
<script src="Views/ScriptTimelineDataGridNode.js"></script>
862863
<script src="Views/ScriptTimelineOverviewGraph.js"></script>
863864
<script src="Views/ScrubberNavigationItem.js"></script>
865+
<script src="Views/SearchResultsPlaceholderTreeElement.js"></script>
864866
<script src="Views/SearchResultTreeElement.js"></script>
865867
<script src="Views/SearchSidebarPanel.js"></script>
866868
<script src="Views/ShaderProgramContentView.js"></script>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (C) 2026 Apple Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
* THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
.tree-outline .item.search-results-placeholder > button {
27+
border: none;
28+
background: none;
29+
text-decoration: underline;
30+
padding-inline: 0;
31+
32+
& + button {
33+
padding-inline-start: 5px;
34+
}
35+
36+
&:first-of-type {
37+
margin-inline-start: var(--tree-outline-icon-margin-start);
38+
}
39+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (C) 2026 Apple Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
* THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
WI.SearchResultsPlaceholderTreeElement = class SearchResultsPlaceholderTreeElement extends WI.TreeElement
27+
{
28+
constructor(delegate, incrementCount, remainingCount)
29+
{
30+
const title = "";
31+
super(title);
32+
33+
console.assert(delegate?.searchResultsPlaceholderLoadMoreResults, delegate);
34+
console.assert(incrementCount > 0, incrementCount);
35+
console.assert(remainingCount > 0, remainingCount);
36+
37+
this._delegate = delegate;
38+
this._incrementCount = Math.min(incrementCount, remainingCount);
39+
this._remainingCount = remainingCount > incrementCount ? remainingCount : 0;
40+
}
41+
42+
// Protected
43+
44+
onattach()
45+
{
46+
if (this._incrementCount) {
47+
this._showMoreButtonElement = this._listItemNode.appendChild(document.createElement("button"));
48+
this._showMoreButtonElement.textContent = WI.UIString("Show %d More").format(this._incrementCount);
49+
this._showMoreButtonElement.addEventListener("click", this._handleShowMoreButtonClicked.bind(this));
50+
}
51+
52+
if (this._remainingCount) {
53+
this._showAllButtonElement = this._listItemNode.appendChild(document.createElement("button"));
54+
this._showAllButtonElement.textContent = WI.UIString("Show All (%d More)").format(this._remainingCount);
55+
this._showAllButtonElement.addEventListener("click", this._handleShowAllButtonClicked.bind(this));
56+
}
57+
58+
this._listItemNode.classList.add("item", "search-results-placeholder");
59+
}
60+
61+
// Private
62+
63+
_handleShowMoreButtonClicked(event)
64+
{
65+
this._delegate.searchResultsPlaceholderLoadMoreResults?.(this, this._incrementCount);
66+
}
67+
68+
_handleShowAllButtonClicked(event)
69+
{
70+
this._delegate.searchResultsPlaceholderLoadMoreResults?.(this, this._remainingCount);
71+
}
72+
}

0 commit comments

Comments
 (0)