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