Web Inspector: Audit: add default tests
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / NavigationSidebarPanel.js
1 /*
2  * Copyright (C) 2013, 2015 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.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel
27 {
28     constructor(identifier, displayName, shouldAutoPruneStaleTopLevelResourceTreeElements, wantsTopOverflowShadow)
29     {
30         super(identifier, displayName);
31
32         this.element.classList.add("navigation");
33
34         this.contentView.element.addEventListener("scroll", this.soon._updateContentOverflowShadowVisibility);
35
36         this._contentTreeOutlineGroup = new WI.TreeOutlineGroup;
37         this._contentTreeOutline = this.createContentTreeOutline();
38
39         this._filterBar = new WI.FilterBar;
40         this._filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._filterDidChange, this);
41         this.element.appendChild(this._filterBar.element);
42
43         this._bottomOverflowShadowElement = document.createElement("div");
44         this._bottomOverflowShadowElement.className = WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName;
45         this.element.appendChild(this._bottomOverflowShadowElement);
46
47         if (wantsTopOverflowShadow) {
48             this._topOverflowShadowElement = this.element.appendChild(document.createElement("div"));
49             this._topOverflowShadowElement.classList.add(WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName, "top");
50         }
51
52         this._boundUpdateContentOverflowShadowVisibility = this.soon._updateContentOverflowShadowVisibility;
53         window.addEventListener("resize", this._boundUpdateContentOverflowShadowVisibility);
54
55         this._filtersSetting = new WI.Setting(identifier + "-navigation-sidebar-filters", {});
56         this._filterBar.filters = this._filtersSetting.value;
57
58         this._emptyContentPlaceholderElements = new Map;
59         this._emptyFilterResults = new Map;
60
61         this._shouldAutoPruneStaleTopLevelResourceTreeElements = shouldAutoPruneStaleTopLevelResourceTreeElements || false;
62
63         if (this._shouldAutoPruneStaleTopLevelResourceTreeElements) {
64             WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._checkForStaleResources, this);
65             WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._checkForStaleResources, this);
66             WI.Frame.addEventListener(WI.Frame.Event.ResourceWasRemoved, this._checkForStaleResources, this);
67         }
68
69         this._pendingViewStateCookie = null;
70         this._restoringState = false;
71     }
72
73     // Public
74
75     closed()
76     {
77         window.removeEventListener("resize", this._boundUpdateContentOverflowShadowVisibility);
78         WI.Frame.removeEventListener(null, null, this);
79     }
80
81     get contentBrowser()
82     {
83         return this._contentBrowser;
84     }
85
86     set contentBrowser(contentBrowser)
87     {
88         this._contentBrowser = contentBrowser || null;
89     }
90
91     get contentTreeOutline()
92     {
93         return this._contentTreeOutline;
94     }
95
96     get contentTreeOutlines()
97     {
98         return Array.from(this._contentTreeOutlineGroup);
99     }
100
101     get currentRepresentedObject()
102     {
103         if (!this._contentBrowser)
104             return null;
105
106         return this._contentBrowser.currentRepresentedObjects[0] || null;
107     }
108
109     get filterBar()
110     {
111         return this._filterBar;
112     }
113
114     get restoringState()
115     {
116         return this._restoringState;
117     }
118
119     cancelRestoringState()
120     {
121         if (!this._finalAttemptToRestoreViewStateTimeout)
122             return;
123
124         clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
125         this._finalAttemptToRestoreViewStateTimeout = undefined;
126     }
127
128     createContentTreeOutline(suppressFiltering)
129     {
130         let contentTreeOutline = new WI.TreeOutline;
131         contentTreeOutline.allowsRepeatSelection = true;
132         contentTreeOutline.element.classList.add(WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName);
133
134         this._contentTreeOutlineGroup.add(contentTreeOutline);
135
136         this.contentView.element.appendChild(contentTreeOutline.element);
137
138         if (!suppressFiltering) {
139             contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementAdded, this._treeElementAddedOrChanged, this);
140             contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementDidChange, this._treeElementAddedOrChanged, this);
141             contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange, this);
142         }
143
144         contentTreeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol] = suppressFiltering;
145
146         return contentTreeOutline;
147     }
148
149     suppressFilteringOnTreeElements(treeElements)
150     {
151         console.assert(Array.isArray(treeElements), "TreeElements should be an array.");
152
153         for (let treeElement of treeElements)
154             treeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol] = true;
155
156         this._updateFilter();
157     }
158
159     treeElementForRepresentedObject(representedObject)
160     {
161         let treeElement = null;
162         for (let treeOutline of this.contentTreeOutlines) {
163             treeElement = treeOutline.getCachedTreeElement(representedObject);
164             if (treeElement)
165                 break;
166         }
167
168         return treeElement;
169     }
170
171     showDefaultContentView()
172     {
173         // Implemented by subclasses if needed to show a content view when no existing tree element is selected.
174     }
175
176     showDefaultContentViewForTreeElement(treeElement)
177     {
178         console.assert(treeElement);
179         console.assert(treeElement.representedObject);
180         if (!treeElement || !treeElement.representedObject)
181             return false;
182
183         // FIXME: <https://webkit.org/b/153634> Web Inspector: some background tabs think they are the foreground tab and do unnecessary work
184         // Do not steal a content view if we are not the active tab/sidebar.
185         if (!this.selected) {
186             let contentView = this.contentBrowser.contentViewForRepresentedObject(treeElement.representedObject);
187             if (contentView && contentView.parentContainer && contentView.parentContainer !== this.contentBrowser.contentViewContainer)
188                 return false;
189
190             // contentView.parentContainer may be null. Check for selected tab, too.
191             let selectedTabContentView = WI.tabBrowser.selectedTabContentView;
192             if (selectedTabContentView && selectedTabContentView.contentBrowser !== this.contentBrowser)
193                 return false;
194         }
195
196         let contentView = this.contentBrowser.showContentViewForRepresentedObject(treeElement.representedObject);
197         if (!contentView)
198             return false;
199
200         treeElement.revealAndSelect(true, false, true, true);
201         return true;
202     }
203
204     canShowRepresentedObject(representedObject)
205     {
206         let selectedTabContentView = WI.tabBrowser.selectedTabContentView;
207         console.assert(selectedTabContentView instanceof WI.TabContentView, "Missing TabContentView for NavigationSidebarPanel.");
208         return selectedTabContentView && selectedTabContentView.canShowRepresentedObject(representedObject);
209     }
210
211     saveStateToCookie(cookie)
212     {
213         console.assert(cookie);
214
215         if (!this._contentBrowser)
216             return;
217
218         let representedObject = this.currentRepresentedObject;
219         if (!representedObject)
220             return;
221
222         cookie[WI.TypeIdentifierCookieKey] = representedObject.constructor.TypeIdentifier;
223
224         if (representedObject.saveIdentityToCookie) {
225             representedObject.saveIdentityToCookie(cookie);
226             return;
227         }
228
229         console.error("NavigationSidebarPanel representedObject is missing a saveIdentityToCookie implementation.", representedObject);
230     }
231
232     // This can be supplemented by subclasses that admit a simpler strategy for static tree elements.
233     restoreStateFromCookie(cookie, relaxedMatchDelay)
234     {
235         this._pendingViewStateCookie = cookie;
236         this._restoringState = true;
237
238         // Check if any existing tree elements in any outline match the cookie.
239         this._checkOutlinesForPendingViewStateCookie();
240
241         if (this._finalAttemptToRestoreViewStateTimeout)
242             clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
243
244         if (relaxedMatchDelay === 0)
245             return;
246
247         function finalAttemptToRestoreViewStateFromCookie()
248         {
249             this._finalAttemptToRestoreViewStateTimeout = undefined;
250
251             this._checkOutlinesForPendingViewStateCookie(true);
252
253             this._pendingViewStateCookie = null;
254             this._restoringState = false;
255         }
256
257         // If the specific tree element wasn't found, we may need to wait for the resources
258         // to be registered. We try one last time (match type only) after an arbitrary amount of timeout.
259         this._finalAttemptToRestoreViewStateTimeout = setTimeout(finalAttemptToRestoreViewStateFromCookie.bind(this), relaxedMatchDelay);
260     }
261
262     showEmptyContentPlaceholder(message, treeOutline)
263     {
264         console.assert(message);
265
266         treeOutline = treeOutline || this._contentTreeOutline;
267
268         let emptyContentPlaceholderElement = this._createEmptyContentPlaceholderIfNeeded(treeOutline, message);
269         if (emptyContentPlaceholderElement.parentNode)
270             return emptyContentPlaceholderElement;
271
272         let emptyContentPlaceholderParentElement = treeOutline.element.parentNode;
273         emptyContentPlaceholderParentElement.appendChild(emptyContentPlaceholderElement);
274
275         this._updateContentOverflowShadowVisibility();
276
277         return emptyContentPlaceholderElement;
278     }
279
280     hideEmptyContentPlaceholder(treeOutline)
281     {
282         treeOutline = treeOutline || this._contentTreeOutline;
283
284         let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline);
285         if (!emptyContentPlaceholderElement || !emptyContentPlaceholderElement.parentNode)
286             return;
287
288         emptyContentPlaceholderElement.remove();
289
290         this._updateContentOverflowShadowVisibility();
291     }
292
293     updateEmptyContentPlaceholder(message, treeOutline)
294     {
295         treeOutline = treeOutline || this._contentTreeOutline;
296
297         if (!treeOutline.children.length) {
298             // No tree elements, so no results.
299             this.showEmptyContentPlaceholder(message, treeOutline);
300         } else if (!this._emptyFilterResults.get(treeOutline)) {
301             // There are tree elements, and not all of them are hidden by the filter.
302             this.hideEmptyContentPlaceholder(treeOutline);
303         }
304     }
305
306     updateFilter()
307     {
308         this._updateFilter();
309     }
310
311     shouldFilterPopulate()
312     {
313         // Overridden by subclasses if needed.
314         return this.hasCustomFilters();
315     }
316
317     hasCustomFilters()
318     {
319         // Implemented by subclasses if needed.
320         return false;
321     }
322
323     matchTreeElementAgainstCustomFilters(treeElement)
324     {
325         // Implemented by subclasses if needed.
326         return true;
327     }
328
329     matchTreeElementAgainstFilterFunctions(treeElement)
330     {
331         if (!this._filterFunctions || !this._filterFunctions.length)
332             return true;
333
334         for (var filterFunction of this._filterFunctions) {
335             if (filterFunction(treeElement))
336                 return true;
337         }
338
339         return false;
340     }
341
342     applyFiltersToTreeElement(treeElement)
343     {
344         if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) {
345             // No filters, so make everything visible.
346             treeElement.hidden = false;
347
348             // If this tree element was expanded during filtering, collapse it again.
349             if (treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) {
350                 treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false;
351                 treeElement.collapse();
352             }
353
354             return;
355         }
356
357         var filterableData = treeElement.filterableData || {};
358
359         var flags = {expandTreeElement: false};
360         var filterRegex = this._textFilterRegex;
361
362         function matchTextFilter(inputs)
363         {
364             if (!inputs || !filterRegex)
365                 return true;
366
367             console.assert(inputs instanceof Array, "filterableData.text should be an array of text inputs");
368
369             // Loop over all the inputs and try to match them.
370             for (var input of inputs) {
371                 if (!input)
372                     continue;
373                 if (filterRegex.test(input)) {
374                     flags.expandTreeElement = true;
375                     return true;
376                 }
377             }
378
379             // No inputs matched.
380             return false;
381         }
382
383         function makeVisible()
384         {
385             // Make this element visible.
386             treeElement.hidden = false;
387
388             // Make the ancestors visible and expand them.
389             var currentAncestor = treeElement.parent;
390             while (currentAncestor && !currentAncestor.root) {
391                 currentAncestor.hidden = false;
392
393                 // Only expand if the built-in filters matched, not custom filters.
394                 if (flags.expandTreeElement && !currentAncestor.expanded) {
395                     currentAncestor.__wasExpandedDuringFiltering = true;
396                     currentAncestor.expand();
397                 }
398
399                 currentAncestor = currentAncestor.parent;
400             }
401         }
402
403         let suppressFiltering = treeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol];
404
405         if (suppressFiltering || (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstFilterFunctions(treeElement, flags) && this.matchTreeElementAgainstCustomFilters(treeElement, flags))) {
406             // Make this element visible since it matches.
407             makeVisible();
408
409             // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again.
410             if (!flags.expandTreeElement && treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) {
411                 treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false;
412                 treeElement.collapse();
413             }
414
415             return;
416         }
417
418         // Make this element invisible since it does not match.
419         treeElement.hidden = true;
420     }
421
422     shown()
423     {
424         super.shown();
425
426         this._updateContentOverflowShadowVisibility();
427     }
428
429     // Protected
430
431     pruneStaleResourceTreeElements()
432     {
433         if (this._checkForStaleResourcesTimeoutIdentifier) {
434             clearTimeout(this._checkForStaleResourcesTimeoutIdentifier);
435             this._checkForStaleResourcesTimeoutIdentifier = undefined;
436         }
437
438         for (let contentTreeOutline of this.contentTreeOutlines) {
439             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
440             // If the parentFrame is no longer in the frame hierarchy we know it was removed due to a navigation or some other page change and
441             // we should remove the issues for that resource.
442             for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
443                 var treeElement = contentTreeOutline.children[i];
444                 if (!(treeElement instanceof WI.ResourceTreeElement))
445                     continue;
446
447                 var resource = treeElement.resource;
448                 if (!resource.parentFrame || resource.parentFrame.isDetached())
449                     contentTreeOutline.removeChildAtIndex(i, true, true);
450             }
451         }
452     }
453
454     // Private
455
456     _updateContentOverflowShadowVisibility()
457     {
458         if (!this.visible)
459             return;
460
461         this._updateContentOverflowShadowVisibility.cancelDebounce();
462
463         let scrollHeight = this.contentView.element.scrollHeight;
464         let offsetHeight = this.contentView.element.offsetHeight;
465
466         if (scrollHeight < offsetHeight) {
467             if (this._topOverflowShadowElement)
468                 this._topOverflowShadowElement.style.opacity = 0;
469             this._bottomOverflowShadowElement.style.opacity = 0;
470             return;
471         }
472
473         let edgeThreshold = 1;
474         let scrollTop = this.contentView.element.scrollTop;
475
476         let topCoverage = Math.min(scrollTop, edgeThreshold);
477         let bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold));
478
479         if (this._topOverflowShadowElement)
480             this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
481         this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
482     }
483
484     _checkForEmptyFilterResults()
485     {
486         function checkTreeOutlineForEmptyFilterResults(treeOutline)
487         {
488             // No tree elements, so don't touch the empty content placeholder.
489             if (!treeOutline.children.length)
490                 return;
491
492             // Iterate over all the top level tree elements. If any filterable elements are visible, return early.
493             let filterableTreeElementFound = false;
494             let unfilteredTreeElementFound = false;
495             let currentTreeElement = treeOutline.children[0];
496             while (currentTreeElement) {
497                 let suppressFilteringForTreeElement = currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol];
498                 if (!suppressFilteringForTreeElement) {
499                     filterableTreeElementFound = true;
500
501                     if (!currentTreeElement.hidden) {
502                         unfilteredTreeElementFound = true;
503                         break;
504                     }
505                 }
506
507                 currentTreeElement = currentTreeElement.nextSibling;
508             }
509
510             if (unfilteredTreeElementFound || !filterableTreeElementFound) {
511                 this.hideEmptyContentPlaceholder(treeOutline);
512                 this._emptyFilterResults.set(treeOutline, false);
513                 return;
514             }
515
516             // All top level tree elements are hidden, so filtering hid everything. Show a message.
517             this.showEmptyContentPlaceholder(WI.UIString("No Filter Results"), treeOutline);
518             this._emptyFilterResults.set(treeOutline, true);
519         }
520
521         for (let treeOutline of this.contentTreeOutlines) {
522             if (treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
523                 continue;
524
525             checkTreeOutlineForEmptyFilterResults.call(this, treeOutline);
526         }
527     }
528
529     _filterDidChange()
530     {
531         this._updateFilter();
532     }
533
534     _updateFilter()
535     {
536         let selectedTreeElement;
537         for (let treeOutline of this.contentTreeOutlines) {
538             if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
539                 continue;
540
541             selectedTreeElement = treeOutline.selectedTreeElement;
542             if (selectedTreeElement)
543                 break;
544         }
545
546         let filters = this._filterBar.filters;
547         this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
548         this._filtersSetting.value = filters;
549         this._filterFunctions = filters.functions;
550
551         // Don't populate if we don't have any active filters.
552         // We only need to populate when a filter needs to reveal.
553         let dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate();
554
555         // Update all trees that allow filtering.
556         for (let treeOutline of this.contentTreeOutlines) {
557             if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
558                 continue;
559
560             let currentTreeElement = treeOutline.children[0];
561             while (currentTreeElement && !currentTreeElement.root) {
562                 if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) {
563                     const currentTreeElementWasHidden = currentTreeElement.hidden;
564                     this.applyFiltersToTreeElement(currentTreeElement);
565                     if (currentTreeElementWasHidden !== currentTreeElement.hidden)
566                         this._treeElementWasFiltered(currentTreeElement);
567                 }
568
569                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate);
570             }
571         }
572
573         this._checkForEmptyFilterResults();
574         this._updateContentOverflowShadowVisibility();
575     }
576
577     _treeElementAddedOrChanged(event)
578     {
579         // Don't populate if we don't have any active filters.
580         // We only need to populate when a filter needs to reveal.
581         var dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate();
582
583         // Apply the filters to the tree element and its descendants.
584         let treeElement = event.data.element;
585         let currentTreeElement = treeElement;
586         while (currentTreeElement && !currentTreeElement.root) {
587             if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) {
588                 const currentTreeElementWasHidden = currentTreeElement.hidden;
589                 this.applyFiltersToTreeElement(currentTreeElement);
590                 if (currentTreeElementWasHidden !== currentTreeElement.hidden)
591                     this._treeElementWasFiltered(currentTreeElement);
592             }
593
594             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate);
595         }
596
597         this._checkForEmptyFilterResults();
598
599         if (this.visible)
600             this.soon._updateContentOverflowShadowVisibility();
601
602         if (this.selected)
603             this._checkElementsForPendingViewStateCookie([treeElement]);
604     }
605
606     _treeElementDisclosureDidChange(event)
607     {
608         this.soon._updateContentOverflowShadowVisibility();
609     }
610
611     _checkForStaleResourcesIfNeeded()
612     {
613         if (!this._checkForStaleResourcesTimeoutIdentifier || !this._shouldAutoPruneStaleTopLevelResourceTreeElements)
614             return;
615         this.pruneStaleResourceTreeElements();
616     }
617
618     _checkForStaleResources(event)
619     {
620         console.assert(this._shouldAutoPruneStaleTopLevelResourceTreeElements);
621
622         if (this._checkForStaleResourcesTimeoutIdentifier)
623             return;
624
625         // Check on a delay to coalesce multiple calls to _checkForStaleResources.
626         this._checkForStaleResourcesTimeoutIdentifier = setTimeout(this.pruneStaleResourceTreeElements.bind(this));
627     }
628
629     _isTreeElementWithoutRepresentedObject(treeElement)
630     {
631         return treeElement instanceof WI.FolderTreeElement
632             || treeElement instanceof WI.DatabaseHostTreeElement
633             || treeElement instanceof WI.IndexedDatabaseHostTreeElement
634             || treeElement instanceof WI.ApplicationCacheManifestTreeElement
635             || treeElement instanceof WI.ThreadTreeElement
636             || treeElement instanceof WI.IdleTreeElement
637             || treeElement instanceof WI.DOMBreakpointTreeElement
638             || treeElement instanceof WI.EventBreakpointTreeElement
639             || treeElement instanceof WI.XHRBreakpointTreeElement
640             || treeElement instanceof WI.CSSStyleSheetTreeElement
641             || typeof treeElement.representedObject === "string"
642             || treeElement.representedObject instanceof String;
643     }
644
645     _checkOutlinesForPendingViewStateCookie(matchTypeOnly)
646     {
647         if (!this._pendingViewStateCookie)
648             return;
649
650         this._checkForStaleResourcesIfNeeded();
651
652         var visibleTreeElements = [];
653         this.contentTreeOutlines.forEach(function(outline) {
654             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
655             while (currentTreeElement) {
656                 visibleTreeElements.push(currentTreeElement);
657                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
658             }
659         });
660
661         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
662     }
663
664     _checkElementsForPendingViewStateCookie(treeElements, matchTypeOnly)
665     {
666         if (!this._pendingViewStateCookie)
667             return;
668
669         var cookie = this._pendingViewStateCookie;
670
671         function treeElementMatchesCookie(treeElement)
672         {
673             if (this._isTreeElementWithoutRepresentedObject(treeElement))
674                 return false;
675
676             var representedObject = treeElement.representedObject;
677             if (!representedObject)
678                 return false;
679
680             var typeIdentifier = cookie[WI.TypeIdentifierCookieKey];
681             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
682                 return false;
683
684             if (matchTypeOnly)
685                 return true;
686
687             var candidateObjectCookie = {};
688             if (representedObject.saveIdentityToCookie)
689                 representedObject.saveIdentityToCookie(candidateObjectCookie);
690
691             var candidateCookieKeys = Object.keys(candidateObjectCookie);
692             return candidateCookieKeys.length && candidateCookieKeys.every((key) => candidateObjectCookie[key] === cookie[key]);
693         }
694
695         var matchedElement = null;
696         treeElements.some((element) => {
697             if (treeElementMatchesCookie.call(this, element)) {
698                 matchedElement = element;
699                 return true;
700             }
701             return false;
702         });
703
704         if (matchedElement) {
705             let didShowContentView = this.showDefaultContentViewForTreeElement(matchedElement);
706             if (!didShowContentView)
707                 return;
708
709             this._pendingViewStateCookie = null;
710
711             // Delay clearing the restoringState flag until the next runloop so listeners
712             // checking for it in this runloop still know state was being restored.
713             setTimeout(() => {
714                 this._restoringState = false;
715             }, 0);
716
717             if (this._finalAttemptToRestoreViewStateTimeout) {
718                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
719                 this._finalAttemptToRestoreViewStateTimeout = undefined;
720             }
721         }
722     }
723
724     _createEmptyContentPlaceholderIfNeeded(treeOutline, message)
725     {
726         let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline);
727         if (emptyContentPlaceholderElement)
728             return emptyContentPlaceholderElement;
729
730         emptyContentPlaceholderElement = message instanceof Node ? message : WI.createMessageTextView(message);
731         this._emptyContentPlaceholderElements.set(treeOutline, emptyContentPlaceholderElement);
732
733         return emptyContentPlaceholderElement;
734     }
735
736     _treeElementWasFiltered(treeElement)
737     {
738         if (treeElement.selected || treeElement.hidden)
739             return;
740
741         let representedObject = this.currentRepresentedObject;
742         if (!representedObject || treeElement.representedObject !== representedObject)
743             return;
744
745         const omitFocus = true;
746         const selectedByUser = false;
747         const suppressOnSelect = true;
748         const suppressOnDeselect = true;
749         treeElement.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
750     }
751 };
752
753 WI.NavigationSidebarPanel.SuppressFilteringSymbol = Symbol("suppress-filtering");
754 WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol = Symbol("was-expanded-during-filtering");
755
756 WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
757 WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";