ce3ad4fe17fe6a8b1e029343454302cb49397e56
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / NavigationSidebarPanel.js
1 /*
2  * Copyright (C) 2013 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 = function(identifier, displayName, image, keyboardShortcutKey, autoPruneOldTopLevelResourceTreeElements, autoHideToolbarItemWhenEmpty, wantsTopOverflowShadow, element, role, label) {
27     if (keyboardShortcutKey)
28         this._keyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, keyboardShortcutKey, this.toggle.bind(this));
29
30     if (this._keyboardShortcut) {
31         var showToolTip = WebInspector.UIString("Show the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
32         var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
33     } else {
34         var showToolTip = WebInspector.UIString("Show the %s navigation sidebar").format(displayName);
35         var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar").format(displayName);
36     }
37
38     WebInspector.SidebarPanel.call(this, identifier, displayName, showToolTip, hideToolTip, image, element, role, label || displayName);
39
40     this.element.classList.add(WebInspector.NavigationSidebarPanel.StyleClassName);
41
42     this._autoHideToolbarItemWhenEmpty = autoHideToolbarItemWhenEmpty || false;
43
44     if (autoHideToolbarItemWhenEmpty)
45         this.toolbarItem.hidden = true;
46
47     this._visibleContentTreeOutlines = new Set;
48
49     this._contentElement = document.createElement("div");
50     this._contentElement.className = WebInspector.NavigationSidebarPanel.ContentElementStyleClassName;
51     this._contentElement.addEventListener("scroll", this._updateContentOverflowShadowVisibility.bind(this));
52     this.element.appendChild(this._contentElement);
53
54     this._contentTreeOutline = this.createContentTreeOutline(true);
55
56     this._filterBar = new WebInspector.FilterBar();
57     this._filterBar.addEventListener(WebInspector.FilterBar.Event.TextFilterDidChange, this._textFilterDidChange, this);
58     this.element.appendChild(this._filterBar.element);
59
60     this._bottomOverflowShadowElement = document.createElement("div");
61     this._bottomOverflowShadowElement.className = WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName;
62     this.element.appendChild(this._bottomOverflowShadowElement);
63
64     if (wantsTopOverflowShadow) {
65         this._topOverflowShadowElement = document.createElement("div");
66         this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName);
67         this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName);
68         this.element.appendChild(this._topOverflowShadowElement);
69     }
70
71     window.addEventListener("resize", this._updateContentOverflowShadowVisibility.bind(this));
72
73     this._filtersSetting = new WebInspector.Setting(identifier + "-navigation-sidebar-filters", {});
74     this._filterBar.filters = this._filtersSetting.value;
75
76     this._emptyContentPlaceholderElement = document.createElement("div");
77     this._emptyContentPlaceholderElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName;
78
79     this._emptyContentPlaceholderMessageElement = document.createElement("div");
80     this._emptyContentPlaceholderMessageElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName;
81     this._emptyContentPlaceholderElement.appendChild(this._emptyContentPlaceholderMessageElement);
82
83     this._generateStyleRulesIfNeeded();
84     this._generateDisclosureTrianglesIfNeeded();
85
86     if (autoPruneOldTopLevelResourceTreeElements) {
87         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._checkForOldResources, this);
88         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasRemoved, this._checkForOldResources, this);
89         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasRemoved, this._checkForOldResources, this);
90     }
91 };
92
93 WebInspector.NavigationSidebarPanel.StyleClassName = "navigation";
94 WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
95 WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName = "top";
96 WebInspector.NavigationSidebarPanel.ContentElementStyleClassName = "content";
97 WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName = "hidden";
98 WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";
99 WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName = "hide-disclosure-buttons";
100 WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName = "empty-content-placeholder";
101 WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName = "message";
102 WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-open";
103 WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-closed";
104 WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix = "-normal";
105 WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix = "-selected";
106
107 WebInspector.NavigationSidebarPanel.prototype = {
108     constructor: WebInspector.NavigationSidebarPanel,
109
110     // Public
111
112     get contentElement()
113     {
114         return this._contentElement;
115     },
116
117     get contentTreeOutlineElement()
118     {
119         return this._contentTreeOutline.element;
120     },
121
122     get contentTreeOutline()
123     {
124         return this._contentTreeOutline;
125     },
126
127     set contentTreeOutline(newTreeOutline)
128     {
129         console.assert(newTreeOutline);
130         if (!newTreeOutline)
131             return;
132
133         if (this._contentTreeOutline)
134             this._contentTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
135
136         this._contentTreeOutline = newTreeOutline;
137         this._contentTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
138
139         this._visibleContentTreeOutlines.delete(this._contentTreeOutline);
140         this._visibleContentTreeOutlines.add(newTreeOutline);
141
142         this._updateFilter();
143     },
144
145     get contentTreeOutlineToAutoPrune()
146     {
147         return this._contentTreeOutline;
148     },
149
150     get visibleContentTreeOutlines()
151     {
152         return this._visibleContentTreeOutlines;
153     },
154
155     get hasSelectedElement()
156     {
157         return !!this._contentTreeOutline.selectedTreeElement;
158     },
159
160     get filterBar()
161     {
162         return this._filterBar;
163     },
164
165     get restoringState()
166     {
167         return this._restoringState;
168     },
169
170     createContentTreeOutline: function(dontHideByDefault, suppressFiltering)
171     {
172         var contentTreeOutlineElement = document.createElement("ol");
173         contentTreeOutlineElement.className = WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName;
174         if (!dontHideByDefault)
175             contentTreeOutlineElement.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
176         this._contentElement.appendChild(contentTreeOutlineElement);
177
178         var contentTreeOutline = new TreeOutline(contentTreeOutlineElement);
179         contentTreeOutline.allowsRepeatSelection = true;
180
181         if (!suppressFiltering) {
182             contentTreeOutline.onadd = this._treeElementAddedOrChanged.bind(this);
183             contentTreeOutline.onchange = this._treeElementAddedOrChanged.bind(this);
184             contentTreeOutline.onexpand = this._treeElementExpandedOrCollapsed.bind(this);
185             contentTreeOutline.oncollapse = this._treeElementExpandedOrCollapsed.bind(this);
186         }
187
188         if (dontHideByDefault)
189             this._visibleContentTreeOutlines.add(contentTreeOutline);
190
191         return contentTreeOutline;
192     },
193
194     treeElementForRepresentedObject: function(representedObject)
195     {
196         return this._contentTreeOutline.getCachedTreeElement(representedObject);
197     },
198
199     showDefaultContentView: function()
200     {
201         // Implemneted by subclasses if needed to show a content view when no existing tree element is selected.
202     },
203
204     showContentViewForCurrentSelection: function()
205     {
206         // Reselect the selected tree element to cause the content view to be shown as well. <rdar://problem/10854727>
207         var selectedTreeElement = this._contentTreeOutline.selectedTreeElement;
208         if (selectedTreeElement)
209             selectedTreeElement.select();
210     },
211
212     saveStateToCookie: function(cookie)
213     {
214         console.assert(cookie);
215
216         // This does not save folder selections, which lack a represented object and content view.
217         var selectedTreeElement = null;
218         this._visibleContentTreeOutlines.forEach(function(outline) {
219             if (outline.selectedTreeElement)
220                 selectedTreeElement = outline.selectedTreeElement;
221         });
222
223         if (!selectedTreeElement)
224             return;
225
226         if (this._isTreeElementWithoutRepresentedObject(selectedTreeElement))
227             return;
228
229         var representedObject = selectedTreeElement.representedObject;
230         cookie[WebInspector.TypeIdentifierCookieKey] = representedObject.constructor.TypeIdentifier;
231
232         if (representedObject.saveIdentityToCookie)
233             representedObject.saveIdentityToCookie(cookie);
234         else
235             console.error("Error: TreeElement.representedObject is missing a saveIdentityToCookie implementation. TreeElement.constructor: %s", selectedTreeElement.constructor);
236     },
237
238     // This can be supplemented by subclasses that admit a simpler strategy for static tree elements.
239     restoreStateFromCookie: function(cookie, relaxedMatchDelay)
240     {
241         this._pendingViewStateCookie = cookie;
242         this._restoringState = true;
243
244         // Check if any existing tree elements in any outline match the cookie.
245         this._checkOutlinesForPendingViewStateCookie();
246
247         if (this._finalAttemptToRestoreViewStateTimeout)
248             clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
249
250         function finalAttemptToRestoreViewStateFromCookie()
251         {
252             delete this._finalAttemptToRestoreViewStateTimeout;
253
254             this._checkOutlinesForPendingViewStateCookie(true);
255
256             delete this._pendingViewStateCookie;
257             delete this._restoringState;
258         }
259
260         // If the specific tree element wasn't found, we may need to wait for the resources
261         // to be registered. We try one last time (match type only) after an arbitrary amount of timeout.
262         this._finalAttemptToRestoreViewStateTimeout = setTimeout(finalAttemptToRestoreViewStateFromCookie.bind(this), relaxedMatchDelay);
263     },
264
265     showEmptyContentPlaceholder: function(message, hideToolbarItem)
266     {
267         console.assert(message);
268
269         if (this._emptyContentPlaceholderElement.parentNode && this._emptyContentPlaceholderMessageElement.textContent === message)
270             return;
271
272         this._emptyContentPlaceholderMessageElement.textContent = message;
273         this.element.appendChild(this._emptyContentPlaceholderElement);
274
275         this._hideToolbarItemWhenEmpty = hideToolbarItem || false;
276         this._updateToolbarItemVisibility();
277         this._updateContentOverflowShadowVisibility();
278     },
279
280     hideEmptyContentPlaceholder: function()
281     {
282         if (!this._emptyContentPlaceholderElement.parentNode)
283             return;
284
285         this._emptyContentPlaceholderElement.parentNode.removeChild(this._emptyContentPlaceholderElement);
286
287         this._hideToolbarItemWhenEmpty = false;
288         this._updateToolbarItemVisibility();
289         this._updateContentOverflowShadowVisibility();
290     },
291
292     updateEmptyContentPlaceholder: function(message)
293     {
294         this._updateToolbarItemVisibility();
295
296         if (!this._contentTreeOutline.children.length) {
297             // No tree elements, so no results.
298             this.showEmptyContentPlaceholder(message);
299         } else if (!this._emptyFilterResults) {
300             // There are tree elements, and not all of them are hidden by the filter.
301             this.hideEmptyContentPlaceholder();
302         }
303     },
304
305     updateFilter: function()
306     {
307         this._updateFilter();
308     },
309
310     hasCustomFilters: function()
311     {
312         // Implemented by subclasses if needed.
313         return false;
314     },
315
316     matchTreeElementAgainstCustomFilters: function(treeElement)
317     {
318         // Implemented by subclasses if needed.
319         return true;
320     },
321
322     applyFiltersToTreeElement: function(treeElement)
323     {
324         if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) {
325             // No filters, so make everything visible.
326             treeElement.hidden = false;
327
328             // If this tree element was expanded during filtering, collapse it again.
329             if (treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
330                 delete treeElement.__wasExpandedDuringFiltering;
331                 treeElement.collapse();
332             }
333
334             return;
335         }
336
337         var filterableData = treeElement.filterableData || {};
338
339         var matchedBuiltInFilters = false;
340
341         var self = this;
342         function matchTextFilter(inputs)
343         {
344             if (!inputs || !self._textFilterRegex)
345                 return true;
346
347             // Convert to a single item array if needed.
348             if (!(inputs instanceof Array))
349                 inputs = [inputs];
350
351             // Loop over all the inputs and try to match them.
352             for (var input of inputs) {
353                 if (!input)
354                     continue;
355                 if (self._textFilterRegex.test(input)) {
356                     matchedBuiltInFilters = true;
357                     return true;
358                 }
359             }
360
361             // No inputs matched.
362             return false;
363         }
364
365         function makeVisible()
366         {
367             // Make this element visible.
368             treeElement.hidden = false;
369
370             // Make the ancestors visible and expand them.
371             var currentAncestor = treeElement.parent;
372             while (currentAncestor && !currentAncestor.root) {
373                 currentAncestor.hidden = false;
374
375                 // Only expand if the built-in filters matched, not custom filters.
376                 if (matchedBuiltInFilters && !currentAncestor.expanded) {
377                     currentAncestor.__wasExpandedDuringFiltering = true;
378                     currentAncestor.expand();
379                 }
380
381                 currentAncestor = currentAncestor.parent;
382             }
383         }
384
385         if (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstCustomFilters(treeElement)) {
386             // Make this element visible since it matches.
387             makeVisible();
388
389             // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again.
390             if (!matchedBuiltInFilters && treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
391                 delete treeElement.__wasExpandedDuringFiltering;
392                 treeElement.collapse();
393             }
394
395             return;
396         }
397
398         // Make this element invisible since it does not match.
399         treeElement.hidden = true;
400     },
401
402     show: function()
403     {
404         if (!this.parentSidebar)
405             return;
406
407         WebInspector.SidebarPanel.prototype.show.call(this);
408
409         this.contentTreeOutlineElement.focus();
410     },
411
412     shown: function()
413     {
414         WebInspector.SidebarPanel.prototype.shown.call(this);
415
416         this._updateContentOverflowShadowVisibility();
417
418         // Force the navigation item to be visible. This makes sure it is
419         // always visible when the panel is shown.
420         this.toolbarItem.hidden = false;
421     },
422
423     hidden: function()
424     {
425         WebInspector.SidebarPanel.prototype.hidden.call(this);
426
427         this._updateToolbarItemVisibility();
428     },
429
430     // Private
431     
432     _updateContentOverflowShadowVisibilitySoon: function()
433     {
434         if (this._updateContentOverflowShadowVisibilityIdentifier)
435             return;
436
437         this._updateContentOverflowShadowVisibilityIdentifier = setTimeout(this._updateContentOverflowShadowVisibility.bind(this), 0);
438     },
439
440     _updateContentOverflowShadowVisibility: function()
441     {
442         delete this._updateContentOverflowShadowVisibilityIdentifier;
443
444         var scrollHeight = this._contentElement.scrollHeight;
445         var offsetHeight = this._contentElement.offsetHeight;
446
447         if (scrollHeight < offsetHeight) {
448             if (this._topOverflowShadowElement)
449                 this._topOverflowShadowElement.style.opacity = 0;
450             this._bottomOverflowShadowElement.style.opacity = 0;
451             return;
452         }
453
454         if (WebInspector.Platform.isLegacyMacOS)
455             const edgeThreshold = 10;
456         else
457             const edgeThreshold = 1;
458
459         var scrollTop = this._contentElement.scrollTop;
460
461         var topCoverage = Math.min(scrollTop, edgeThreshold);
462         var bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold));
463
464         if (this._topOverflowShadowElement)
465             this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
466         this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
467     },
468
469     _updateToolbarItemVisibility: function()
470     {
471         // Hide the navigation item if requested or auto-hiding and we are not visible and we are empty.
472         var shouldHide = ((this._hideToolbarItemWhenEmpty || this._autoHideToolbarItemWhenEmpty) && !this.selected && !this._contentTreeOutline.children.length);
473         this.toolbarItem.hidden = shouldHide;
474     },
475
476     _checkForEmptyFilterResults: function()
477     {
478         // No tree elements, so don't touch the empty content placeholder.
479         if (!this._contentTreeOutline.children.length)
480             return;
481
482         // Iterate over all the top level tree elements. If any are visible, return early.
483         var currentTreeElement = this._contentTreeOutline.children[0];
484         while (currentTreeElement) {
485             if (!currentTreeElement.hidden) {
486                 // Not hidden, so hide any empty content message.
487                 this.hideEmptyContentPlaceholder();
488                 this._emptyFilterResults = false;
489                 return;
490             }
491
492             currentTreeElement = currentTreeElement.nextSibling;
493         }
494
495         // All top level tree elements are hidden, so filtering hid everything. Show a message.
496         this.showEmptyContentPlaceholder(WebInspector.UIString("No Filter Results"));
497         this._emptyFilterResults = true;
498     },
499
500     _textFilterDidChange: function()
501     {
502         this._updateFilter();
503     },
504
505     _updateFilter: function()
506     {
507         var filters = this._filterBar.filters;
508         this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
509         this._filtersSetting.value = filters;
510
511         // Don't populate if we don't have any active filters.
512         // We only need to populate when a filter needs to reveal.
513         var dontPopulate = !this._filterBar.hasActiveFilters();
514
515         // Update the whole tree.
516         var currentTreeElement = this._contentTreeOutline.children[0];
517         while (currentTreeElement && !currentTreeElement.root) {
518             this.applyFiltersToTreeElement(currentTreeElement);
519             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate);
520         }
521
522         this._checkForEmptyFilterResults();
523         this._updateContentOverflowShadowVisibility();
524     },
525
526     _treeElementAddedOrChanged: function(treeElement)
527     {
528         // Don't populate if we don't have any active filters.
529         // We only need to populate when a filter needs to reveal.
530         var dontPopulate = !this._filterBar.hasActiveFilters();
531
532         // Apply the filters to the tree element and its descendants.
533         var currentTreeElement = treeElement;
534         while (currentTreeElement && !currentTreeElement.root) {
535             this.applyFiltersToTreeElement(currentTreeElement);
536             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate);
537         }
538
539         this._checkForEmptyFilterResults();
540         this._updateContentOverflowShadowVisibilitySoon();
541
542         if (this.selected)
543             this._checkElementsForPendingViewStateCookie(treeElement);
544     },
545
546     _treeElementExpandedOrCollapsed: function(treeElement)
547     {
548         this._updateContentOverflowShadowVisibility();
549     },
550
551     _generateStyleRulesIfNeeded: function()
552     {
553         if (WebInspector.NavigationSidebarPanel._styleElement)
554             return;
555
556         WebInspector.NavigationSidebarPanel._styleElement = document.createElement("style");
557
558         const maximumSidebarTreeDepth = 32;
559         const baseLeftPadding = 5; // Matches the padding in NavigationSidebarPanel.css for the item class. Keep in sync.
560         const depthPadding = 10;
561
562         var styleText = "";
563         var childrenSubstring = "";
564         for (var i = 1; i <= maximumSidebarTreeDepth; ++i) {
565             // Keep all the elements at the same depth once the maximum is reached.
566             childrenSubstring += i === maximumSidebarTreeDepth ? " .children" : " > .children";
567             styleText += "." + WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName + childrenSubstring + " > .item { ";
568             styleText += "padding-left: " + (baseLeftPadding + (depthPadding * i)) + "px; }\n";
569         }
570
571         WebInspector.NavigationSidebarPanel._styleElement.textContent = styleText;
572
573         document.head.appendChild(WebInspector.NavigationSidebarPanel._styleElement);
574     },
575
576     _generateDisclosureTrianglesIfNeeded: function()
577     {
578         if (WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles)
579             return;
580
581         // Set this early instead of in _generateDisclosureTriangle because we don't want multiple panels that are
582         // created at the same time to duplicate the work (even though it would be harmless.)
583         WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles = true;
584
585         var specifications = {};
586
587         if (WebInspector.Platform.isLegacyMacOS) {
588             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
589                 fillColor: [112, 126, 139],
590                 shadowColor: [255, 255, 255, 0.8],
591                 shadowOffsetX: 0,
592                 shadowOffsetY: 1,
593                 shadowBlur: 0
594             };
595
596             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
597                 fillColor: [255, 255, 255],
598                 shadowColor: [61, 91, 110, 0.8],
599                 shadowOffsetX: 0,
600                 shadowOffsetY: 1,
601                 shadowBlur: 2
602             };
603         } else {
604             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
605                 fillColor: [140, 140, 140]
606             };
607
608             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
609                 fillColor: [255, 255, 255]
610             };
611         }
612
613         generateColoredImagesForCSS("Images/DisclosureTriangleSmallOpen.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier);
614         generateColoredImagesForCSS("Images/DisclosureTriangleSmallClosed.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier);
615     },
616
617     _checkForOldResources: function(event)
618     {
619         if (this._checkForOldResourcesTimeoutIdentifier)
620             return;
621
622         function delayedWork()
623         {
624             delete this._checkForOldResourcesTimeoutIdentifier;
625
626             var contentTreeOutline = this.contentTreeOutlineToAutoPrune;
627
628             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
629             // 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
630             // we should remove the issues for that resource.
631             for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
632                 var treeElement = contentTreeOutline.children[i];
633                 if (!(treeElement instanceof WebInspector.ResourceTreeElement))
634                     continue;
635
636                 var resource = treeElement.resource;
637                 if (!resource.parentFrame || resource.parentFrame.isDetached())
638                     contentTreeOutline.removeChildAtIndex(i, true, true);
639             }
640
641             if (typeof this._updateEmptyContentPlaceholder === "function")
642                 this._updateEmptyContentPlaceholder();
643         }
644
645         // Check on a delay to coalesce multiple calls to _checkForOldResources.
646         this._checkForOldResourcesTimeoutIdentifier = setTimeout(delayedWork.bind(this), 0);
647     },
648
649     _isTreeElementWithoutRepresentedObject: function(treeElement)
650     {
651         return treeElement instanceof WebInspector.FolderTreeElement
652             || treeElement instanceof WebInspector.DatabaseHostTreeElement
653             || typeof treeElement.representedObject === "string"
654             || treeElement.representedObject instanceof String;
655     },
656
657     _checkOutlinesForPendingViewStateCookie: function(matchTypeOnly)
658     {
659         if (!this._pendingViewStateCookie)
660             return;
661
662         var visibleTreeElements = [];
663         this._visibleContentTreeOutlines.forEach(function(outline) {
664             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
665             while (currentTreeElement) {
666                 visibleTreeElements.push(currentTreeElement);
667                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
668             }
669         });
670
671         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
672     },
673
674     _checkElementsForPendingViewStateCookie: function(treeElements, matchTypeOnly)
675     {
676         if (!this._pendingViewStateCookie)
677             return;
678
679         var cookie = this._pendingViewStateCookie;
680
681         function treeElementMatchesCookie(treeElement)
682         {
683             if (this._isTreeElementWithoutRepresentedObject(treeElement))
684                 return false;
685
686             var representedObject = treeElement.representedObject;
687             if (!representedObject)
688                 return false;
689
690             var typeIdentifier = cookie[WebInspector.TypeIdentifierCookieKey];
691             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
692                 return false;
693
694             if (matchTypeOnly)
695                 return true;
696
697             var candidateObjectCookie = {};
698             if (representedObject.saveIdentityToCookie)
699                 representedObject.saveIdentityToCookie(candidateObjectCookie);
700
701             var candidateCookieKeys = Object.keys(candidateObjectCookie);
702             return candidateCookieKeys.length && candidateCookieKeys.every(function valuesMatchForKey(key) {
703                 return candidateObjectCookie[key] === cookie[key];
704             });
705         }
706
707         if (!(treeElements instanceof Array))
708             treeElements = [treeElements];
709
710         var matchedElement = null;
711         treeElements.some(function(element) {
712             if (treeElementMatchesCookie.call(this, element)) {
713                 matchedElement = element;
714                 return true;
715             }
716         }, this);
717
718         if (matchedElement) {
719             matchedElement.revealAndSelect();
720
721             delete this._pendingViewStateCookie;
722
723             // Delay clearing the restoringState flag until the next runloop so listeners
724             // checking for it in this runloop still know state was being restored.
725             setTimeout(function() {
726                 delete this._restoringState;
727             }.bind(this));
728
729             if (this._finalAttemptToRestoreViewStateTimeout) {
730                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
731                 delete this._finalAttemptToRestoreViewStateTimeout;
732             }
733         }
734     }
735 };
736
737 WebInspector.NavigationSidebarPanel.prototype.__proto__ = WebInspector.SidebarPanel.prototype;