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