Web Inspector: Remove unused NavigationSidebarPanel.treeElementAddedOrChanged
[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         // Overriden 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.XHRBreakpointTreeElement
637             || treeElement instanceof WI.CSSStyleSheetTreeElement
638             || typeof treeElement.representedObject === "string"
639             || treeElement.representedObject instanceof String;
640     }
641
642     _checkOutlinesForPendingViewStateCookie(matchTypeOnly)
643     {
644         if (!this._pendingViewStateCookie)
645             return;
646
647         this._checkForStaleResourcesIfNeeded();
648
649         var visibleTreeElements = [];
650         this.contentTreeOutlines.forEach(function(outline) {
651             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
652             while (currentTreeElement) {
653                 visibleTreeElements.push(currentTreeElement);
654                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
655             }
656         });
657
658         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
659     }
660
661     _checkElementsForPendingViewStateCookie(treeElements, matchTypeOnly)
662     {
663         if (!this._pendingViewStateCookie)
664             return;
665
666         var cookie = this._pendingViewStateCookie;
667
668         function treeElementMatchesCookie(treeElement)
669         {
670             if (this._isTreeElementWithoutRepresentedObject(treeElement))
671                 return false;
672
673             var representedObject = treeElement.representedObject;
674             if (!representedObject)
675                 return false;
676
677             var typeIdentifier = cookie[WI.TypeIdentifierCookieKey];
678             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
679                 return false;
680
681             if (matchTypeOnly)
682                 return true;
683
684             var candidateObjectCookie = {};
685             if (representedObject.saveIdentityToCookie)
686                 representedObject.saveIdentityToCookie(candidateObjectCookie);
687
688             var candidateCookieKeys = Object.keys(candidateObjectCookie);
689             return candidateCookieKeys.length && candidateCookieKeys.every((key) => candidateObjectCookie[key] === cookie[key]);
690         }
691
692         var matchedElement = null;
693         treeElements.some((element) => {
694             if (treeElementMatchesCookie.call(this, element)) {
695                 matchedElement = element;
696                 return true;
697             }
698             return false;
699         });
700
701         if (matchedElement) {
702             let didShowContentView = this.showDefaultContentViewForTreeElement(matchedElement);
703             if (!didShowContentView)
704                 return;
705
706             this._pendingViewStateCookie = null;
707
708             // Delay clearing the restoringState flag until the next runloop so listeners
709             // checking for it in this runloop still know state was being restored.
710             setTimeout(() => {
711                 this._restoringState = false;
712             }, 0);
713
714             if (this._finalAttemptToRestoreViewStateTimeout) {
715                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
716                 this._finalAttemptToRestoreViewStateTimeout = undefined;
717             }
718         }
719     }
720
721     _createEmptyContentPlaceholderIfNeeded(treeOutline, message)
722     {
723         let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline);
724         if (emptyContentPlaceholderElement)
725             return emptyContentPlaceholderElement;
726
727         emptyContentPlaceholderElement = WI.createMessageTextView(message);
728         this._emptyContentPlaceholderElements.set(treeOutline, emptyContentPlaceholderElement);
729
730         return emptyContentPlaceholderElement;
731     }
732
733     _treeElementWasFiltered(treeElement)
734     {
735         if (treeElement.selected || treeElement.hidden)
736             return;
737
738         let representedObject = this.currentRepresentedObject;
739         if (!representedObject || treeElement.representedObject !== representedObject)
740             return;
741
742         const omitFocus = true;
743         const selectedByUser = false;
744         const suppressOnSelect = true;
745         const suppressOnDeselect = true;
746         treeElement.revealAndSelect(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect);
747     }
748 };
749
750 WI.NavigationSidebarPanel.SuppressFilteringSymbol = Symbol("suppress-filtering");
751 WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol = Symbol("was-expanded-during-filtering");
752
753 WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
754 WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";