3ab1dbaf74a6675b8e690957df7ac8cf5a3729f8
[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;
271
272         let emptyContentPlaceholderParentElement = treeOutline.element.parentNode;
273         emptyContentPlaceholderParentElement.appendChild(emptyContentPlaceholderElement);
274
275         this._updateContentOverflowShadowVisibility();
276     }
277
278     hideEmptyContentPlaceholder(treeOutline)
279     {
280         treeOutline = treeOutline || this._contentTreeOutline;
281
282         let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline);
283         if (!emptyContentPlaceholderElement || !emptyContentPlaceholderElement.parentNode)
284             return;
285
286         emptyContentPlaceholderElement.remove();
287
288         this._updateContentOverflowShadowVisibility();
289     }
290
291     updateEmptyContentPlaceholder(message, treeOutline)
292     {
293         treeOutline = treeOutline || this._contentTreeOutline;
294
295         if (!treeOutline.children.length) {
296             // No tree elements, so no results.
297             this.showEmptyContentPlaceholder(message, treeOutline);
298         } else if (!this._emptyFilterResults.get(treeOutline)) {
299             // There are tree elements, and not all of them are hidden by the filter.
300             this.hideEmptyContentPlaceholder(treeOutline);
301         }
302     }
303
304     updateFilter()
305     {
306         this._updateFilter();
307     }
308
309     shouldFilterPopulate()
310     {
311         // Overridden by subclasses if needed.
312         return this.hasCustomFilters();
313     }
314
315     hasCustomFilters()
316     {
317         // Implemented by subclasses if needed.
318         return false;
319     }
320
321     matchTreeElementAgainstCustomFilters(treeElement)
322     {
323         // Implemented by subclasses if needed.
324         return true;
325     }
326
327     matchTreeElementAgainstFilterFunctions(treeElement)
328     {
329         if (!this._filterFunctions || !this._filterFunctions.length)
330             return true;
331
332         for (var filterFunction of this._filterFunctions) {
333             if (filterFunction(treeElement))
334                 return true;
335         }
336
337         return false;
338     }
339
340     applyFiltersToTreeElement(treeElement)
341     {
342         if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) {
343             // No filters, so make everything visible.
344             treeElement.hidden = false;
345
346             // If this tree element was expanded during filtering, collapse it again.
347             if (treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) {
348                 treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false;
349                 treeElement.collapse();
350             }
351
352             return;
353         }
354
355         var filterableData = treeElement.filterableData || {};
356
357         var flags = {expandTreeElement: false};
358         var filterRegex = this._textFilterRegex;
359
360         function matchTextFilter(inputs)
361         {
362             if (!inputs || !filterRegex)
363                 return true;
364
365             console.assert(inputs instanceof Array, "filterableData.text should be an array of text inputs");
366
367             // Loop over all the inputs and try to match them.
368             for (var input of inputs) {
369                 if (!input)
370                     continue;
371                 if (filterRegex.test(input)) {
372                     flags.expandTreeElement = true;
373                     return true;
374                 }
375             }
376
377             // No inputs matched.
378             return false;
379         }
380
381         function makeVisible()
382         {
383             // Make this element visible.
384             treeElement.hidden = false;
385
386             // Make the ancestors visible and expand them.
387             var currentAncestor = treeElement.parent;
388             while (currentAncestor && !currentAncestor.root) {
389                 currentAncestor.hidden = false;
390
391                 // Only expand if the built-in filters matched, not custom filters.
392                 if (flags.expandTreeElement && !currentAncestor.expanded) {
393                     currentAncestor.__wasExpandedDuringFiltering = true;
394                     currentAncestor.expand();
395                 }
396
397                 currentAncestor = currentAncestor.parent;
398             }
399         }
400
401         let suppressFiltering = treeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol];
402
403         if (suppressFiltering || (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstFilterFunctions(treeElement, flags) && this.matchTreeElementAgainstCustomFilters(treeElement, flags))) {
404             // Make this element visible since it matches.
405             makeVisible();
406
407             // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again.
408             if (!flags.expandTreeElement && treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) {
409                 treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false;
410                 treeElement.collapse();
411             }
412
413             return;
414         }
415
416         // Make this element invisible since it does not match.
417         treeElement.hidden = true;
418     }
419
420     shown()
421     {
422         super.shown();
423
424         this._updateContentOverflowShadowVisibility();
425     }
426
427     // Protected
428
429     pruneStaleResourceTreeElements()
430     {
431         if (this._checkForStaleResourcesTimeoutIdentifier) {
432             clearTimeout(this._checkForStaleResourcesTimeoutIdentifier);
433             this._checkForStaleResourcesTimeoutIdentifier = undefined;
434         }
435
436         for (let contentTreeOutline of this.contentTreeOutlines) {
437             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
438             // 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
439             // we should remove the issues for that resource.
440             for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
441                 var treeElement = contentTreeOutline.children[i];
442                 if (!(treeElement instanceof WI.ResourceTreeElement))
443                     continue;
444
445                 var resource = treeElement.resource;
446                 if (!resource.parentFrame || resource.parentFrame.isDetached())
447                     contentTreeOutline.removeChildAtIndex(i, true, true);
448             }
449         }
450     }
451
452     // Private
453
454     _updateContentOverflowShadowVisibility()
455     {
456         if (!this.visible)
457             return;
458
459         this._updateContentOverflowShadowVisibility.cancelDebounce();
460
461         let scrollHeight = this.contentView.element.scrollHeight;
462         let offsetHeight = this.contentView.element.offsetHeight;
463
464         if (scrollHeight < offsetHeight) {
465             if (this._topOverflowShadowElement)
466                 this._topOverflowShadowElement.style.opacity = 0;
467             this._bottomOverflowShadowElement.style.opacity = 0;
468             return;
469         }
470
471         let edgeThreshold = 1;
472         let scrollTop = this.contentView.element.scrollTop;
473
474         let topCoverage = Math.min(scrollTop, edgeThreshold);
475         let bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold));
476
477         if (this._topOverflowShadowElement)
478             this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
479         this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
480     }
481
482     _checkForEmptyFilterResults()
483     {
484         function checkTreeOutlineForEmptyFilterResults(treeOutline)
485         {
486             // No tree elements, so don't touch the empty content placeholder.
487             if (!treeOutline.children.length)
488                 return;
489
490             // Iterate over all the top level tree elements. If any filterable elements are visible, return early.
491             let filterableTreeElementFound = false;
492             let unfilteredTreeElementFound = false;
493             let currentTreeElement = treeOutline.children[0];
494             while (currentTreeElement) {
495                 let suppressFilteringForTreeElement = currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol];
496                 if (!suppressFilteringForTreeElement) {
497                     filterableTreeElementFound = true;
498
499                     if (!currentTreeElement.hidden) {
500                         unfilteredTreeElementFound = true;
501                         break;
502                     }
503                 }
504
505                 currentTreeElement = currentTreeElement.nextSibling;
506             }
507
508             if (unfilteredTreeElementFound || !filterableTreeElementFound) {
509                 this.hideEmptyContentPlaceholder(treeOutline);
510                 this._emptyFilterResults.set(treeOutline, false);
511                 return;
512             }
513
514             // All top level tree elements are hidden, so filtering hid everything. Show a message.
515             this.showEmptyContentPlaceholder(WI.UIString("No Filter Results"), treeOutline);
516             this._emptyFilterResults.set(treeOutline, true);
517         }
518
519         for (let treeOutline of this.contentTreeOutlines) {
520             if (treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
521                 continue;
522
523             checkTreeOutlineForEmptyFilterResults.call(this, treeOutline);
524         }
525     }
526
527     _filterDidChange()
528     {
529         this._updateFilter();
530     }
531
532     _updateFilter()
533     {
534         let selectedTreeElement;
535         for (let treeOutline of this.contentTreeOutlines) {
536             if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
537                 continue;
538
539             selectedTreeElement = treeOutline.selectedTreeElement;
540             if (selectedTreeElement)
541                 break;
542         }
543
544         let filters = this._filterBar.filters;
545         this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
546         this._filtersSetting.value = filters;
547         this._filterFunctions = filters.functions;
548
549         // Don't populate if we don't have any active filters.
550         // We only need to populate when a filter needs to reveal.
551         let dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate();
552
553         // Update all trees that allow filtering.
554         for (let treeOutline of this.contentTreeOutlines) {
555             if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
556                 continue;
557
558             let currentTreeElement = treeOutline.children[0];
559             while (currentTreeElement && !currentTreeElement.root) {
560                 if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) {
561                     const currentTreeElementWasHidden = currentTreeElement.hidden;
562                     this.applyFiltersToTreeElement(currentTreeElement);
563                     if (currentTreeElementWasHidden !== currentTreeElement.hidden)
564                         this._treeElementWasFiltered(currentTreeElement);
565                 }
566
567                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate);
568             }
569         }
570
571         this._checkForEmptyFilterResults();
572         this._updateContentOverflowShadowVisibility();
573     }
574
575     _treeElementAddedOrChanged(event)
576     {
577         // Don't populate if we don't have any active filters.
578         // We only need to populate when a filter needs to reveal.
579         var dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate();
580
581         // Apply the filters to the tree element and its descendants.
582         let treeElement = event.data.element;
583         let currentTreeElement = treeElement;
584         while (currentTreeElement && !currentTreeElement.root) {
585             if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) {
586                 const currentTreeElementWasHidden = currentTreeElement.hidden;
587                 this.applyFiltersToTreeElement(currentTreeElement);
588                 if (currentTreeElementWasHidden !== currentTreeElement.hidden)
589                     this._treeElementWasFiltered(currentTreeElement);
590             }
591
592             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate);
593         }
594
595         this._checkForEmptyFilterResults();
596
597         if (this.visible)
598             this.soon._updateContentOverflowShadowVisibility();
599
600         if (this.selected)
601             this._checkElementsForPendingViewStateCookie([treeElement]);
602     }
603
604     _treeElementDisclosureDidChange(event)
605     {
606         this.soon._updateContentOverflowShadowVisibility();
607     }
608
609     _checkForStaleResourcesIfNeeded()
610     {
611         if (!this._checkForStaleResourcesTimeoutIdentifier || !this._shouldAutoPruneStaleTopLevelResourceTreeElements)
612             return;
613         this.pruneStaleResourceTreeElements();
614     }
615
616     _checkForStaleResources(event)
617     {
618         console.assert(this._shouldAutoPruneStaleTopLevelResourceTreeElements);
619
620         if (this._checkForStaleResourcesTimeoutIdentifier)
621             return;
622
623         // Check on a delay to coalesce multiple calls to _checkForStaleResources.
624         this._checkForStaleResourcesTimeoutIdentifier = setTimeout(this.pruneStaleResourceTreeElements.bind(this));
625     }
626
627     _isTreeElementWithoutRepresentedObject(treeElement)
628     {
629         return treeElement instanceof WI.FolderTreeElement
630             || treeElement instanceof WI.DatabaseHostTreeElement
631             || treeElement instanceof WI.IndexedDatabaseHostTreeElement
632             || treeElement instanceof WI.ApplicationCacheManifestTreeElement
633             || treeElement instanceof WI.ThreadTreeElement
634             || treeElement instanceof WI.IdleTreeElement
635             || treeElement instanceof WI.DOMBreakpointTreeElement
636             || treeElement instanceof WI.EventBreakpointTreeElement
637             || treeElement instanceof WI.XHRBreakpointTreeElement
638             || treeElement instanceof WI.CSSStyleSheetTreeElement
639             || typeof treeElement.representedObject === "string"
640             || treeElement.representedObject instanceof String;
641     }
642
643     _checkOutlinesForPendingViewStateCookie(matchTypeOnly)
644     {
645         if (!this._pendingViewStateCookie)
646             return;
647
648         this._checkForStaleResourcesIfNeeded();
649
650         var visibleTreeElements = [];
651         this.contentTreeOutlines.forEach(function(outline) {
652             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
653             while (currentTreeElement) {
654                 visibleTreeElements.push(currentTreeElement);
655                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
656             }
657         });
658
659         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
660     }
661
662     _checkElementsForPendingViewStateCookie(treeElements, matchTypeOnly)
663     {
664         if (!this._pendingViewStateCookie)
665             return;
666
667         var cookie = this._pendingViewStateCookie;
668
669         function treeElementMatchesCookie(treeElement)
670         {
671             if (this._isTreeElementWithoutRepresentedObject(treeElement))
672                 return false;
673
674             var representedObject = treeElement.representedObject;
675             if (!representedObject)
676                 return false;
677
678             var typeIdentifier = cookie[WI.TypeIdentifierCookieKey];
679             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
680                 return false;
681
682             if (matchTypeOnly)
683                 return true;
684
685             var candidateObjectCookie = {};
686             if (representedObject.saveIdentityToCookie)
687                 representedObject.saveIdentityToCookie(candidateObjectCookie);
688
689             var candidateCookieKeys = Object.keys(candidateObjectCookie);
690             return candidateCookieKeys.length && candidateCookieKeys.every((key) => candidateObjectCookie[key] === cookie[key]);
691         }
692
693         var matchedElement = null;
694         treeElements.some((element) => {
695             if (treeElementMatchesCookie.call(this, element)) {
696                 matchedElement = element;
697                 return true;
698             }
699             return false;
700         });
701
702         if (matchedElement) {
703             let didShowContentView = this.showDefaultContentViewForTreeElement(matchedElement);
704             if (!didShowContentView)
705                 return;
706
707             this._pendingViewStateCookie = null;
708
709             // Delay clearing the restoringState flag until the next runloop so listeners
710             // checking for it in this runloop still know state was being restored.
711             setTimeout(() => {
712                 this._restoringState = false;
713             }, 0);
714
715             if (this._finalAttemptToRestoreViewStateTimeout) {
716                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
717                 this._finalAttemptToRestoreViewStateTimeout = undefined;
718             }
719         }
720     }
721
722     _createEmptyContentPlaceholderIfNeeded(treeOutline, message)
723     {
724         let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline);
725         if (emptyContentPlaceholderElement)
726             return emptyContentPlaceholderElement;
727
728         emptyContentPlaceholderElement = WI.createMessageTextView(message);
729         this._emptyContentPlaceholderElements.set(treeOutline, emptyContentPlaceholderElement);
730
731         return emptyContentPlaceholderElement;
732     }
733
734     _treeElementWasFiltered(treeElement)
735     {
736         if (treeElement.selected || treeElement.hidden)
737             return;
738
739         let representedObject = this.currentRepresentedObject;
740         if (!representedObject || treeElement.representedObject !== representedObject)
741             return;
742
743         const omitFocus = true;
744         const selectedByUser = false;
745         const suppressOnSelect = true;
746         const suppressOnDeselect = true;
747         treeElement.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
748     }
749 };
750
751 WI.NavigationSidebarPanel.SuppressFilteringSymbol = Symbol("suppress-filtering");
752 WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol = Symbol("was-expanded-during-filtering");
753
754 WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
755 WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";