Web Inspector: Make showing a content view work in the tab world
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ResourceSidebarPanel.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.ResourceSidebarPanel = class ResourceSidebarPanel extends WebInspector.NavigationSidebarPanel
27 {
28     constructor(contentBrowser)
29     {
30         super("resource", WebInspector.UIString("Resources"), true, true);
31
32         this.contentBrowser = contentBrowser;
33
34         var searchElement = document.createElement("div");
35         searchElement.classList.add("search-bar");
36         this.element.appendChild(searchElement);
37
38         this._inputElement = document.createElement("input");
39         this._inputElement.type = "search";
40         this._inputElement.spellcheck = false;
41         this._inputElement.addEventListener("search", this._searchFieldChanged.bind(this));
42         this._inputElement.addEventListener("input", this._searchFieldInput.bind(this));
43         this._inputElement.setAttribute("results", 5);
44         this._inputElement.setAttribute("autosave", "inspector-search");
45         this._inputElement.setAttribute("placeholder", WebInspector.UIString("Search Resource Content"));
46         searchElement.appendChild(this._inputElement);
47
48         this.filterBar.placeholder = WebInspector.UIString("Filter Resource List");
49
50         this._waitingForInitialMainFrame = true;
51         this._lastSearchedPageSetting = new WebInspector.Setting("last-searched-page", null);
52
53         this._searchQuerySetting = new WebInspector.Setting("search-sidebar-query", "");
54         this._inputElement.value = this._searchQuerySetting.value;
55
56         this._searchKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "F", this._focusSearchField.bind(this));
57
58         this._localStorageRootTreeElement = null;
59         this._sessionStorageRootTreeElement = null;
60
61         this._databaseRootTreeElement = null;
62         this._databaseHostTreeElementMap = {};
63
64         this._indexedDatabaseRootTreeElement = null;
65         this._indexedDatabaseHostTreeElementMap = {};
66
67         this._cookieStorageRootTreeElement = null;
68
69         this._applicationCacheRootTreeElement = null;
70         this._applicationCacheURLTreeElementMap = {};
71
72         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.CookieStorageObjectWasAdded, this._cookieStorageObjectWasAdded, this);
73         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DOMStorageObjectWasAdded, this._domStorageObjectWasAdded, this);
74         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DOMStorageObjectWasInspected, this._domStorageObjectWasInspected, this);
75         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DatabaseWasAdded, this._databaseWasAdded, this);
76         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DatabaseWasInspected, this._databaseWasInspected, this);
77         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.IndexedDatabaseWasAdded, this._indexedDatabaseWasAdded, this);
78         WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.Cleared, this._storageCleared, this);
79
80         WebInspector.applicationCacheManager.addEventListener(WebInspector.ApplicationCacheManager.Event.FrameManifestAdded, this._frameManifestAdded, this);
81         WebInspector.applicationCacheManager.addEventListener(WebInspector.ApplicationCacheManager.Event.FrameManifestRemoved, this._frameManifestRemoved, this);
82
83         WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
84
85         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptWasAdded, this);
86         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
87
88         WebInspector.notifications.addEventListener(WebInspector.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
89
90         this._resourcesContentTreeOutline = this.contentTreeOutline;
91         this._searchContentTreeOutline = this.createContentTreeOutline();
92
93         this._resourcesContentTreeOutline.onselect = this._treeElementSelected.bind(this);
94         this._searchContentTreeOutline.onselect = this._treeElementSelected.bind(this);
95
96         this._resourcesContentTreeOutline.includeSourceMapResourceChildren = true;
97
98         if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
99             this._resourcesContentTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
100     }
101
102     // Public
103
104     showDefaultContentView()
105     {
106         if (WebInspector.frameResourceManager.mainFrame) {
107             this.contentBrowser.showContentViewForRepresentedObject(WebInspector.frameResourceManager.mainFrame);
108             return;
109         }
110
111         var firstTreeElement = this._resourcesContentTreeOutline.children[0];
112         if (firstTreeElement)
113             firstTreeElement.revealAndSelect();
114     }
115
116     get contentTreeOutlineToAutoPrune()
117     {
118         return this._searchContentTreeOutline;
119     }
120
121     treeElementForRepresentedObject(representedObject)
122     {
123         // A custom implementation is needed for this since the frames are populated lazily.
124
125         if (!this._mainFrameTreeElement && (representedObject instanceof WebInspector.Resource || representedObject instanceof WebInspector.Frame)) {
126             // All resources are under the main frame, so we need to return early if we don't have the main frame yet.
127             return null;
128         }
129
130         // The Frame is used as the representedObject instead of the main resource in our tree.
131         if (representedObject instanceof WebInspector.Resource && representedObject.parentFrame && representedObject.parentFrame.mainResource === representedObject)
132             representedObject = representedObject.parentFrame;
133
134         function isAncestor(ancestor, resourceOrFrame)
135         {
136             // SourceMapResources are descendants of another SourceCode object.
137             if (resourceOrFrame instanceof WebInspector.SourceMapResource) {
138                 if (resourceOrFrame.sourceMap.originalSourceCode === ancestor)
139                     return true;
140
141                 // Not a direct ancestor, so check the ancestors of the parent SourceCode object.
142                 resourceOrFrame = resourceOrFrame.sourceMap.originalSourceCode;
143             }
144
145             var currentFrame = resourceOrFrame.parentFrame;
146             while (currentFrame) {
147                 if (currentFrame === ancestor)
148                     return true;
149                 currentFrame = currentFrame.parentFrame;
150             }
151
152             return false;
153         }
154
155         function getParent(resourceOrFrame)
156         {
157             // SourceMapResources are descendants of another SourceCode object.
158             if (resourceOrFrame instanceof WebInspector.SourceMapResource)
159                 return resourceOrFrame.sourceMap.originalSourceCode;
160             return resourceOrFrame.parentFrame;
161         }
162
163         var treeElement = this._resourcesContentTreeOutline.findTreeElement(representedObject, isAncestor, getParent);
164         if (treeElement)
165             return treeElement;
166
167         // Only special case Script objects.
168         if (!(representedObject instanceof WebInspector.Script))
169             return null;
170
171         // If the Script has a URL we should have found it earlier.
172         if (representedObject.url) {
173             console.error("Didn't find a ScriptTreeElement for a Script with a URL.");
174             return null;
175         }
176
177         // Since the Script does not have a URL we consider it an 'anonymous' script. These scripts happen from calls to
178         // window.eval() or browser features like Auto Fill and Reader. They are not normally added to the sidebar, but since
179         // we have a ScriptContentView asking for the tree element we will make a ScriptTreeElement on demand and add it.
180
181         if (!this._anonymousScriptsFolderTreeElement)
182             this._anonymousScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Anonymous Scripts"));
183
184         if (!this._anonymousScriptsFolderTreeElement.parent) {
185             var index = insertionIndexForObjectInListSortedByFunction(this._anonymousScriptsFolderTreeElement, this._resourcesContentTreeOutline.children, this._compareTreeElements);
186             this._resourcesContentTreeOutline.insertChild(this._anonymousScriptsFolderTreeElement, index);
187         }
188
189         var scriptTreeElement = new WebInspector.ScriptTreeElement(representedObject);
190         this._anonymousScriptsFolderTreeElement.appendChild(scriptTreeElement);
191
192         return scriptTreeElement;
193     }
194
195     performSearch(searchTerm)
196     {
197         // Before performing a new search, clear the old search.
198         this._searchContentTreeOutline.removeChildren();
199
200         this._inputElement.value = searchTerm;
201         this._searchQuerySetting.value = searchTerm;
202         this._lastSearchedPageSetting.value = searchTerm && WebInspector.frameResourceManager.mainFrame ? WebInspector.frameResourceManager.mainFrame.url.hash : null;
203
204         this.hideEmptyContentPlaceholder();
205
206         searchTerm = searchTerm.trim();
207         if (!searchTerm.length) {
208             this._showResourcesContentTreeOutline();
209             return;
210         }
211
212         this._showSearchContentTreeOutline();
213
214         // FIXME: Provide UI to toggle regex and case sensitive searches.
215         var isCaseSensitive = false;
216         var isRegex = false;
217
218         var updateEmptyContentPlaceholderTimeout = null;
219
220         function updateEmptyContentPlaceholderSoon()
221         {
222             if (updateEmptyContentPlaceholderTimeout)
223                 return;
224             updateEmptyContentPlaceholderTimeout = setTimeout(updateEmptyContentPlaceholder.bind(this), 100);
225         }
226
227         function updateEmptyContentPlaceholder()
228         {
229             if (updateEmptyContentPlaceholderTimeout) {
230                 clearTimeout(updateEmptyContentPlaceholderTimeout);
231                 updateEmptyContentPlaceholderTimeout = null;
232             }
233
234             this.updateEmptyContentPlaceholder(WebInspector.UIString("No Search Results"));
235         }
236
237         function forEachMatch(searchTerm, lineContent, callback)
238         {
239             var lineMatch;
240             var searchRegex = new RegExp(searchTerm.escapeForRegExp(), "gi");
241             while ((searchRegex.lastIndex < lineContent.length) && (lineMatch = searchRegex.exec(lineContent)))
242                 callback(lineMatch, searchRegex.lastIndex);
243         }
244
245         function resourcesCallback(error, result)
246         {
247             updateEmptyContentPlaceholderSoon.call(this);
248
249             if (error)
250                 return;
251
252             function resourceCallback(url, error, resourceMatches)
253             {
254                 updateEmptyContentPlaceholderSoon.call(this);
255
256                 if (error || !resourceMatches || !resourceMatches.length)
257                     return;
258
259                 var frame = WebInspector.frameResourceManager.frameForIdentifier(searchResult.frameId);
260                 if (!frame)
261                     return;
262
263                 var resource = frame.url === url ? frame.mainResource : frame.resourceForURL(url);
264                 if (!resource)
265                     return;
266
267                 var resourceTreeElement = this._searchTreeElementForResource(resource);
268
269                 for (var i = 0; i < resourceMatches.length; ++i) {
270                     var match = resourceMatches[i];
271                     forEachMatch(searchTerm, match.lineContent, function(lineMatch, lastIndex) {
272                         var matchObject = new WebInspector.SourceCodeSearchMatchObject(resource, match.lineContent, searchTerm, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex));
273                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
274                         resourceTreeElement.appendChild(matchTreeElement);
275                     });
276                 }
277
278                 updateEmptyContentPlaceholder.call(this);
279             }
280
281             for (var i = 0; i < result.length; ++i) {
282                 var searchResult = result[i];
283                 if (!searchResult.url || !searchResult.frameId)
284                     continue;
285
286                 PageAgent.searchInResource(searchResult.frameId, searchResult.url, searchTerm, isCaseSensitive, isRegex, resourceCallback.bind(this, searchResult.url));
287             }
288         }
289
290         function searchScripts(scriptsToSearch)
291         {
292             updateEmptyContentPlaceholderSoon.call(this);
293
294             if (!scriptsToSearch.length)
295                 return;
296
297             function scriptCallback(script, error, scriptMatches)
298             {
299                 updateEmptyContentPlaceholderSoon.call(this);
300
301                 if (error || !scriptMatches || !scriptMatches.length)
302                     return;
303
304                 var scriptTreeElement = this._searchTreeElementForScript(script);
305
306                 for (var i = 0; i < scriptMatches.length; ++i) {
307                     var match = scriptMatches[i];
308                     forEachMatch(searchTerm, match.lineContent, function(lineMatch, lastIndex) {
309                         var matchObject = new WebInspector.SourceCodeSearchMatchObject(script, match.lineContent, searchTerm, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex));
310                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
311                         scriptTreeElement.appendChild(matchTreeElement);
312                     });
313                 }
314
315                 updateEmptyContentPlaceholder.call(this);
316             }
317
318             for (var script of scriptsToSearch)
319                 DebuggerAgent.searchInContent(script.id, searchTerm, isCaseSensitive, isRegex, scriptCallback.bind(this, script));
320         }
321
322         function domCallback(error, searchId, resultsCount)
323         {
324             updateEmptyContentPlaceholderSoon.call(this);
325
326             if (error || !resultsCount)
327                 return;
328
329             this._domSearchIdentifier = searchId;
330
331             function domSearchResults(error, nodeIds)
332             {
333                 updateEmptyContentPlaceholderSoon.call(this);
334
335                 if (error)
336                     return;
337
338                 for (var i = 0; i < nodeIds.length; ++i) {
339                     // If someone started a new search, then return early and stop showing seach results from the old query.
340                     if (this._domSearchIdentifier !== searchId)
341                         return;
342
343                     var domNode = WebInspector.domTreeManager.nodeForId(nodeIds[i]);
344                     if (!domNode || !domNode.ownerDocument)
345                         continue;
346
347                     // We do not display the document node when the search query is "/". We don't have anything to display in the content view for it.
348                     if (domNode.nodeType() === Node.DOCUMENT_NODE)
349                         continue;
350
351                     // FIXME: This should use a frame to do resourceForURL, but DOMAgent does not provide a frameId.
352                     var resource = WebInspector.frameResourceManager.resourceForURL(domNode.ownerDocument.documentURL);
353                     if (!resource)
354                         continue;
355
356                     var resourceTreeElement = this._searchTreeElementForResource(resource);
357                     var domNodeTitle = WebInspector.DOMSearchMatchObject.titleForDOMNode(domNode);
358
359                     // Textual matches.
360                     var didFindTextualMatch = false;
361                     forEachMatch(searchTerm, domNodeTitle, function(lineMatch, lastIndex) {
362                         var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, searchTerm, new WebInspector.TextRange(0, lineMatch.index, 0, lastIndex));
363                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
364                         resourceTreeElement.appendChild(matchTreeElement);
365                         didFindTextualMatch = true;
366                     });
367
368                     // Non-textual matches are CSS Selector or XPath matches. In such cases, display the node entirely highlighted.
369                     if (!didFindTextualMatch) {
370                         var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, domNodeTitle, new WebInspector.TextRange(0, 0, 0, domNodeTitle.length));
371                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
372                         resourceTreeElement.appendChild(matchTreeElement);
373                     }
374
375                     updateEmptyContentPlaceholder.call(this);
376                 }
377             }
378
379             DOMAgent.getSearchResults(searchId, 0, resultsCount, domSearchResults.bind(this));
380         }
381
382         if (window.DOMAgent)
383             WebInspector.domTreeManager.requestDocument();
384
385         if (window.PageAgent)
386             PageAgent.searchInResources(searchTerm, isCaseSensitive, isRegex, resourcesCallback.bind(this));
387
388         setTimeout(searchScripts.bind(this, this._scriptsToSearch()), 0);
389
390         if (window.DOMAgent) {
391             if ("_domSearchIdentifier" in this) {
392                 DOMAgent.discardSearchResults(this._domSearchIdentifier);
393                 delete this._domSearchIdentifier;
394             }
395
396             DOMAgent.performSearch(searchTerm, domCallback.bind(this));
397         }
398
399         // FIXME: Resource search should work in JSContext inspection.
400         // <https://webkit.org/b/131252> Web Inspector: JSContext inspection Resource search does not work
401         if (!window.DOMAgent && !window.PageAgent)
402             updateEmptyContentPlaceholderSoon.call(this);
403     }
404
405     // Private
406
407     _showResourcesContentTreeOutline()
408     {
409         this.filterBar.placeholder = WebInspector.UIString("Filter Resource List");
410         this.contentTreeOutline = this._resourcesContentTreeOutline;
411     }
412
413     _showSearchContentTreeOutline()
414     {
415         this.filterBar.placeholder = WebInspector.UIString("Filter Search Results");
416         this.contentTreeOutline = this._searchContentTreeOutline;
417     }
418
419     _searchFieldChanged(event)
420     {
421         this.performSearch(event.target.value);
422     }
423
424     _searchFieldInput(event)
425     {
426         // If the search field is cleared, immediately clear the search results tree outline.
427         if (!event.target.value.length && this.contentTreeOutline === this._searchContentTreeOutline)
428             this.performSearch("");
429     }
430
431     _searchTreeElementForResource(resource)
432     {
433         var resourceTreeElement = this._searchContentTreeOutline.getCachedTreeElement(resource);
434         if (!resourceTreeElement) {
435             resourceTreeElement = new WebInspector.ResourceTreeElement(resource);
436             resourceTreeElement.hasChildren = true;
437             resourceTreeElement.expand();
438
439             this._searchContentTreeOutline.appendChild(resourceTreeElement);
440         }
441
442         return resourceTreeElement;
443     }
444
445     _searchTreeElementForScript(script)
446     {
447         var scriptTreeElement = this._searchContentTreeOutline.getCachedTreeElement(script);
448         if (!scriptTreeElement) {
449             scriptTreeElement = new WebInspector.ScriptTreeElement(script);
450             scriptTreeElement.hasChildren = true;
451             scriptTreeElement.expand();
452
453             this._searchContentTreeOutline.appendChild(scriptTreeElement);
454         }
455
456         return scriptTreeElement;
457     }
458
459     _focusSearchField(keyboardShortcut, event)
460     {
461         this.show();
462
463         this._inputElement.select();
464     }
465
466     _mainFrameDidChange(event)
467     {
468         if (this._mainFrameTreeElement) {
469             this._mainFrameTreeElement.frame.removeEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
470             this._resourcesContentTreeOutline.removeChild(this._mainFrameTreeElement);
471             this._mainFrameTreeElement = null;
472         }
473
474         var newFrame = WebInspector.frameResourceManager.mainFrame;
475         if (newFrame) {
476             newFrame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
477             this._mainFrameTreeElement = new WebInspector.FrameTreeElement(newFrame);
478             this._resourcesContentTreeOutline.insertChild(this._mainFrameTreeElement, 0);
479
480             // Select a tree element by default. Allow onselect if we aren't showing a content view.
481             if (!this._resourcesContentTreeOutline.selectedTreeElement) {
482                 var currentContentView = this.contentBrowser.currentContentView;
483                 var treeElement = currentContentView ? this.treeElementForRepresentedObject(currentContentView.representedObject) : null;
484                 if (!treeElement)
485                     treeElement = this._mainFrameTreeElement;
486                 treeElement.revealAndSelect(true, false, !!currentContentView, true);
487             }
488         }
489
490         // The navigation path needs update when the main frame changes, since all resources are under the main frame.
491         this.contentBrowser.updateHierarchicalPathForCurrentContentView();
492
493         // We only care about the first time the main frame changes.
494         if (!this._waitingForInitialMainFrame)
495             return;
496
497         // Only if there is a main frame.
498         if (!newFrame)
499             return;
500
501         this._waitingForInitialMainFrame = false;
502
503         // Only if the last page searched is the same as the current page.
504         if (this._lastSearchedPageSetting.value !== newFrame.url.hash)
505             return;
506
507         // Search for whatever is in the input field. This was populated with the last used search term.
508         this.performSearch(this._inputElement.value);
509     }
510
511     _mainFrameMainResourceDidChange(event)
512     {
513         var wasShowingResourceSidebar = this.selected;
514         var currentContentView = this.contentBrowser.currentContentView;
515         var wasShowingResourceContentView = currentContentView instanceof WebInspector.ResourceContentView
516             || currentContentView instanceof WebInspector.ResourceClusterContentView
517             || currentContentView instanceof WebInspector.FrameContentView
518             || currentContentView instanceof WebInspector.ScriptContentView;
519
520         this.contentBrowser.contentViewContainer.closeAllContentViews();
521
522         // Break out of search tree outline if there was an active search.
523         this._showResourcesContentTreeOutline();
524
525         function delayedWork()
526         {
527             // Show the main frame since there is no content view showing or we were showing a resource before.
528             // Cookie restoration will attempt to re-select the resource we were showing.
529             if (!this.contentBrowser.currentContentView || wasShowingResourceContentView) {
530                 // NOTE: This selection, during provisional loading, won't be saved, so it is
531                 // safe to do and not-clobber cookie restoration.
532                 this._mainFrameTreeElement.revealAndSelect(true, false);
533             }
534         }
535
536         // Delay this work because other listeners of this event might not have fired yet. So selecting the main frame
537         // before those listeners do their work might cause the content of the old page to show instead of the new page.
538         setTimeout(delayedWork.bind(this), 0);
539     }
540
541     _scriptWasAdded(event)
542     {
543         var script = event.data.script;
544
545         // We don't add scripts without URLs here. Those scripts can quickly clutter the interface and
546         // are usually more transient. They will get added if/when they need to be shown in a content view.
547         if (!script.url)
548             return;
549
550         // Exclude inspector scripts.
551         if (script.url.startsWith("__WebInspector"))
552             return;
553
554         // If the script URL matches a resource we can assume it is part of that resource and does not need added.
555         if (script.resource)
556             return;
557
558         var insertIntoTopLevel = false;
559
560         if (script.injected) {
561             if (!this._extensionScriptsFolderTreeElement)
562                 this._extensionScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extension Scripts"));
563             var parentFolderTreeElement = this._extensionScriptsFolderTreeElement;
564         } else {
565             if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript && !WebInspector.hasExtraDomains)
566                 insertIntoTopLevel = true;
567             else {
568                 if (!this._extraScriptsFolderTreeElement)
569                     this._extraScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extra Scripts"));
570                 var parentFolderTreeElement = this._extraScriptsFolderTreeElement;
571             }
572         }
573
574         var scriptTreeElement = new WebInspector.ScriptTreeElement(script);
575
576         if (insertIntoTopLevel) {
577             var index = insertionIndexForObjectInListSortedByFunction(scriptTreeElement, this._resourcesContentTreeOutline.children, this._compareTreeElements);
578             this._resourcesContentTreeOutline.insertChild(scriptTreeElement, index);
579         } else {
580             if (!parentFolderTreeElement.parent) {
581                 var index = insertionIndexForObjectInListSortedByFunction(parentFolderTreeElement, this._resourcesContentTreeOutline.children, this._compareTreeElements);
582                 this._resourcesContentTreeOutline.insertChild(parentFolderTreeElement, index);
583             }
584
585             parentFolderTreeElement.appendChild(scriptTreeElement);
586         }
587     }
588
589     _scriptsCleared(event)
590     {
591         if (this._extensionScriptsFolderTreeElement) {
592             if (this._extensionScriptsFolderTreeElement.parent)
593                 this._extensionScriptsFolderTreeElement.parent.removeChild(this._extensionScriptsFolderTreeElement);
594             this._extensionScriptsFolderTreeElement = null;
595         }
596
597         if (this._extraScriptsFolderTreeElement) {
598             if (this._extraScriptsFolderTreeElement.parent)
599                 this._extraScriptsFolderTreeElement.parent.removeChild(this._extraScriptsFolderTreeElement);
600             this._extraScriptsFolderTreeElement = null;
601         }
602
603         if (this._anonymousScriptsFolderTreeElement) {
604             if (this._anonymousScriptsFolderTreeElement.parent)
605                 this._anonymousScriptsFolderTreeElement.parent.removeChild(this._anonymousScriptsFolderTreeElement);
606             this._anonymousScriptsFolderTreeElement = null;
607         }
608     }
609
610     _scriptsToSearch(event)
611     {
612         var nonResourceScripts = [];
613
614         function collectFromTreeElement(folderTreeElement)
615         {
616             if (!folderTreeElement)
617                 return;
618
619             var children = folderTreeElement.children;
620             for (var treeElement of children) {
621                 if (treeElement instanceof WebInspector.ScriptTreeElement)
622                     nonResourceScripts.push(treeElement.script);
623             }
624         }
625
626         if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript && !WebInspector.hasExtraDomains)
627             collectFromTreeElement(this._resourcesContentTreeOutline);
628         else {
629             collectFromTreeElement(this._extensionScriptsFolderTreeElement);
630             collectFromTreeElement(this._extraScriptsFolderTreeElement);
631             collectFromTreeElement(this._anonymousScriptsFolderTreeElement);
632         }
633
634         return nonResourceScripts;
635     }
636
637     _treeElementSelected(treeElement, selectedByUser)
638     {
639         if (treeElement instanceof WebInspector.FolderTreeElement || treeElement instanceof WebInspector.DatabaseHostTreeElement ||
640             treeElement instanceof WebInspector.IndexedDatabaseHostTreeElement || treeElement instanceof WebInspector.IndexedDatabaseTreeElement)
641             return;
642
643         if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement ||
644             treeElement instanceof WebInspector.StorageTreeElement || treeElement instanceof WebInspector.DatabaseTableTreeElement ||
645             treeElement instanceof WebInspector.DatabaseTreeElement || treeElement instanceof WebInspector.ApplicationCacheFrameTreeElement ||
646             treeElement instanceof WebInspector.ContentFlowTreeElement || treeElement instanceof WebInspector.IndexedDatabaseObjectStoreTreeElement ||
647             treeElement instanceof WebInspector.IndexedDatabaseObjectStoreIndexTreeElement) {
648             WebInspector.showRepresentedObject(treeElement.representedObject);
649             return;
650         }
651
652         console.assert(treeElement instanceof WebInspector.SearchResultTreeElement);
653         if (!(treeElement instanceof WebInspector.SearchResultTreeElement))
654             return;
655
656         if (treeElement.representedObject instanceof WebInspector.DOMSearchMatchObject)
657             WebInspector.showMainFrameDOMTree(treeElement.representedObject.domNode);
658         else if (treeElement.representedObject instanceof WebInspector.SourceCodeSearchMatchObject)
659             WebInspector.showOriginalOrFormattedSourceCodeTextRange(treeElement.representedObject.sourceCodeTextRange);
660     }
661
662     _domStorageObjectWasAdded(event)
663     {
664         var domStorage = event.data.domStorage;
665         var storageElement = new WebInspector.DOMStorageTreeElement(domStorage);
666
667         if (domStorage.isLocalStorage())
668             this._localStorageRootTreeElement = this._addStorageChild(storageElement, this._localStorageRootTreeElement, WebInspector.UIString("Local Storage"));
669         else
670             this._sessionStorageRootTreeElement = this._addStorageChild(storageElement, this._sessionStorageRootTreeElement, WebInspector.UIString("Session Storage"));
671     }
672
673     _domStorageObjectWasInspected(event)
674     {
675         var domStorage = event.data.domStorage;
676         var treeElement = this.treeElementForRepresentedObject(domStorage);
677         treeElement.revealAndSelect(true);
678     }
679
680     _databaseWasAdded(event)
681     {
682         var database = event.data.database;
683
684         console.assert(database instanceof WebInspector.DatabaseObject);
685
686         if (!this._databaseHostTreeElementMap[database.host]) {
687             this._databaseHostTreeElementMap[database.host] = new WebInspector.DatabaseHostTreeElement(database.host);
688             this._databaseRootTreeElement = this._addStorageChild(this._databaseHostTreeElementMap[database.host], this._databaseRootTreeElement, WebInspector.UIString("Databases"));
689         }
690
691         var databaseElement = new WebInspector.DatabaseTreeElement(database);
692         this._databaseHostTreeElementMap[database.host].appendChild(databaseElement);
693     }
694
695     _databaseWasInspected(event)
696     {
697         var database = event.data.database;
698         var treeElement = this.treeElementForRepresentedObject(database);
699         treeElement.revealAndSelect(true);
700     }
701
702     _indexedDatabaseWasAdded(event)
703     {
704         var indexedDatabase = event.data.indexedDatabase;
705
706         console.assert(indexedDatabase instanceof WebInspector.IndexedDatabase);
707
708         if (!this._indexedDatabaseHostTreeElementMap[indexedDatabase.host]) {
709             this._indexedDatabaseHostTreeElementMap[indexedDatabase.host] = new WebInspector.IndexedDatabaseHostTreeElement(indexedDatabase.host);
710             this._indexedDatabaseRootTreeElement = this._addStorageChild(this._indexedDatabaseHostTreeElementMap[indexedDatabase.host], this._indexedDatabaseRootTreeElement, WebInspector.UIString("Indexed Databases"));
711         }
712
713         var indexedDatabaseElement = new WebInspector.IndexedDatabaseTreeElement(indexedDatabase);
714         this._indexedDatabaseHostTreeElementMap[indexedDatabase.host].appendChild(indexedDatabaseElement);
715     }
716
717     _cookieStorageObjectWasAdded(event)
718     {
719         console.assert(event.data.cookieStorage instanceof WebInspector.CookieStorageObject);
720
721         var cookieElement = new WebInspector.CookieStorageTreeElement(event.data.cookieStorage);
722         this._cookieStorageRootTreeElement = this._addStorageChild(cookieElement, this._cookieStorageRootTreeElement, WebInspector.UIString("Cookies"));
723     }
724
725     _frameManifestAdded(event)
726     {
727         var frameManifest = event.data.frameManifest;
728         console.assert(frameManifest instanceof WebInspector.ApplicationCacheFrame);
729
730         var manifest = frameManifest.manifest;
731         var manifestURL = manifest.manifestURL;
732         if (!this._applicationCacheURLTreeElementMap[manifestURL]) {
733             this._applicationCacheURLTreeElementMap[manifestURL] = new WebInspector.ApplicationCacheManifestTreeElement(manifest);
734             this._applicationCacheRootTreeElement = this._addStorageChild(this._applicationCacheURLTreeElementMap[manifestURL], this._applicationCacheRootTreeElement, WebInspector.UIString("Application Cache"));
735         }
736
737         var frameCacheElement = new WebInspector.ApplicationCacheFrameTreeElement(frameManifest);
738         this._applicationCacheURLTreeElementMap[manifestURL].appendChild(frameCacheElement);
739     }
740
741     _frameManifestRemoved(event)
742     {
743          // FIXME: Implement this.
744     }
745
746     _compareTreeElements(a, b)
747     {
748         // Always sort the main frame element first.
749         if (a instanceof WebInspector.FrameTreeElement)
750             return -1;
751         if (b instanceof WebInspector.FrameTreeElement)
752             return 1;
753
754         console.assert(a.mainTitle);
755         console.assert(b.mainTitle);
756
757         return (a.mainTitle || "").localeCompare(b.mainTitle || "");
758     }
759
760     _addStorageChild(childElement, parentElement, folderName)
761     {
762         if (!parentElement) {
763             childElement.flattened = true;
764
765             this._resourcesContentTreeOutline.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, this._resourcesContentTreeOutline.children, this._compareTreeElements));
766
767             return childElement;
768         }
769
770         if (parentElement instanceof WebInspector.StorageTreeElement) {
771             console.assert(parentElement.flattened);
772
773             var previousOnlyChild = parentElement;
774             previousOnlyChild.flattened = false;
775             this._resourcesContentTreeOutline.removeChild(previousOnlyChild);
776
777             var folderElement = new WebInspector.FolderTreeElement(folderName);
778             this._resourcesContentTreeOutline.insertChild(folderElement, insertionIndexForObjectInListSortedByFunction(folderElement, this._resourcesContentTreeOutline.children, this._compareTreeElements));
779
780             folderElement.appendChild(previousOnlyChild);
781             folderElement.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, folderElement.children, this._compareTreeElements));
782
783             return folderElement;
784         }
785
786         console.assert(parentElement instanceof WebInspector.FolderTreeElement);
787         parentElement.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, parentElement.children, this._compareTreeElements));
788
789         return parentElement;
790     }
791
792     _storageCleared(event)
793     {
794         // Close all DOM and cookie storage content views since the main frame has navigated and all storages are cleared.
795         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.CookieStorageContentView);
796         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DOMStorageContentView);
797         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DatabaseTableContentView);
798         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DatabaseContentView);
799         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.ApplicationCacheFrameContentView);
800
801         if (this._localStorageRootTreeElement && this._localStorageRootTreeElement.parent)
802             this._localStorageRootTreeElement.parent.removeChild(this._localStorageRootTreeElement);
803
804         if (this._sessionStorageRootTreeElement && this._sessionStorageRootTreeElement.parent)
805             this._sessionStorageRootTreeElement.parent.removeChild(this._sessionStorageRootTreeElement);
806
807         if (this._databaseRootTreeElement && this._databaseRootTreeElement.parent)
808             this._databaseRootTreeElement.parent.removeChild(this._databaseRootTreeElement);
809
810         if (this._indexedDatabaseRootTreeElement && this._indexedDatabaseRootTreeElement.parent)
811             this._indexedDatabaseRootTreeElement.parent.removeChild(this._indexedDatabaseRootTreeElement);
812
813         if (this._cookieStorageRootTreeElement && this._cookieStorageRootTreeElement.parent)
814             this._cookieStorageRootTreeElement.parent.removeChild(this._cookieStorageRootTreeElement);
815
816         if (this._applicationCacheRootTreeElement && this._applicationCacheRootTreeElement.parent)
817             this._applicationCacheRootTreeElement.parent.removeChild(this._applicationCacheRootTreeElement);
818
819         this._localStorageRootTreeElement = null;
820         this._sessionStorageRootTreeElement = null;
821         this._databaseRootTreeElement = null;
822         this._databaseHostTreeElementMap = {};
823         this._indexedDatabaseRootTreeElement = null;
824         this._indexedDatabaseHostTreeElementMap = {};
825         this._cookieStorageRootTreeElement = null;
826         this._applicationCacheRootTreeElement = null;
827         this._applicationCacheURLTreeElementMap = {};
828     }
829
830     _extraDomainsActivated()
831     {
832         if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
833             this._resourcesContentTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
834     }
835 };