Web Inspector: save and restore source positions in back/forward history
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / ResourceSidebarPanel.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.ResourceSidebarPanel = function() {
27     WebInspector.NavigationSidebarPanel.call(this, "resource", WebInspector.UIString("Resources"), "Images/NavigationItemStorage.svg", "1", true, false, true);
28
29     var searchElement = document.createElement("div");
30     searchElement.classList.add("search-bar");
31     this.element.appendChild(searchElement);
32
33     this._inputElement = document.createElement("input");
34     this._inputElement.type = "search";
35     this._inputElement.spellcheck = false;
36     this._inputElement.addEventListener("search", this._searchFieldChanged.bind(this));
37     this._inputElement.addEventListener("input", this._searchFieldInput.bind(this));
38     this._inputElement.setAttribute("results", 5);
39     this._inputElement.setAttribute("autosave", "inspector-search");
40     this._inputElement.setAttribute("placeholder", WebInspector.UIString("Search Resource Content"));
41     searchElement.appendChild(this._inputElement);
42
43     this.filterBar.placeholder = WebInspector.UIString("Filter Resource List");
44
45     this._waitingForInitialMainFrame = true;
46     this._lastSearchedPageSetting = new WebInspector.Setting("last-searched-page", null);
47
48     this._searchQuerySetting = new WebInspector.Setting("search-sidebar-query", "");
49     this._inputElement.value = this._searchQuerySetting.value;
50
51     this._searchKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "F", this._focusSearchField.bind(this));
52
53     this._localStorageRootTreeElement = null;
54     this._sessionStorageRootTreeElement = null;
55
56     this._databaseRootTreeElement = null;
57     this._databaseHostTreeElementMap = {};
58
59     this._cookieStorageRootTreeElement = null;
60
61     this._applicationCacheRootTreeElement = null;
62     this._applicationCacheURLTreeElementMap = {};
63
64     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.CookieStorageObjectWasAdded, this._cookieStorageObjectWasAdded, this);
65     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DOMStorageObjectWasAdded, this._domStorageObjectWasAdded, this);
66     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DOMStorageObjectWasInspected, this._domStorageObjectWasInspected, this);
67     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DatabaseWasAdded, this._databaseWasAdded, this);
68     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.DatabaseWasInspected, this._databaseWasInspected, this);
69     WebInspector.storageManager.addEventListener(WebInspector.StorageManager.Event.Cleared, this._storageCleared, this);
70
71     WebInspector.applicationCacheManager.addEventListener(WebInspector.ApplicationCacheManager.Event.FrameManifestAdded, this._frameManifestAdded, this);
72     WebInspector.applicationCacheManager.addEventListener(WebInspector.ApplicationCacheManager.Event.FrameManifestRemoved, this._frameManifestRemoved, this);
73
74     WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
75     WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameWasAdded, this);
76
77     WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, this._domNodeWasInspected, this);
78
79     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptWasAdded, this);
80     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
81
82     this._resourcesContentTreeOutline = this.contentTreeOutline;
83     this._searchContentTreeOutline = this.createContentTreeOutline();
84
85     this._resourcesContentTreeOutline.onselect = this._treeElementSelected.bind(this);
86     this._searchContentTreeOutline.onselect = this._treeElementSelected.bind(this);
87
88     this._resourcesContentTreeOutline.includeSourceMapResourceChildren = true;
89 };
90
91 WebInspector.ResourceSidebarPanel.ResourceContentViewCookieType = "resource";
92 WebInspector.ResourceSidebarPanel.CookieStorageContentViewCookieType = "cookie-storage";
93 WebInspector.ResourceSidebarPanel.DatabaseContentViewCookieType = "database";
94 WebInspector.ResourceSidebarPanel.DatabaseTableContentViewCookieType = "database-table";
95 WebInspector.ResourceSidebarPanel.DOMStorageContentViewCookieType = "dom-storage";
96 WebInspector.ResourceSidebarPanel.ApplicationCacheContentViewCookieType = "application-cache";
97
98 WebInspector.ResourceSidebarPanel.prototype = {
99     constructor: WebInspector.ResourceSidebarPanel,
100
101     // Public
102
103     get contentTreeOutlineToAutoPrune()
104     {
105         return this._searchContentTreeOutline;
106     },
107
108     cookieForContentView: function(contentView)
109     {
110         console.assert(contentView instanceof WebInspector.FrameContentView || contentView instanceof WebInspector.ResourceClusterContentView || contentView instanceof WebInspector.ScriptContentView || contentView instanceof WebInspector.CookieStorageContentView || contentView instanceof WebInspector.DOMStorageContentView || contentView instanceof WebInspector.DatabaseTableContentView || contentView instanceof WebInspector.DatabaseContentView || contentView instanceof WebInspector.ApplicationCacheFrameContentView);
111
112         var representedObject = contentView.representedObject;
113
114         // The main frame does not need a URL, an empty cookie is enough.
115         if (representedObject instanceof WebInspector.Frame && representedObject.isMainFrame())
116             return {type: WebInspector.ResourceSidebarPanel.ResourceContentViewCookieType};
117
118         // If it has a URL, return a cookie with the frame/resource/script URL represented by the content view.
119         if (representedObject.url) {
120             console.assert(representedObject instanceof WebInspector.Frame || representedObject instanceof WebInspector.Resource || representedObject instanceof WebInspector.Script);
121             return {type: WebInspector.ResourceSidebarPanel.ResourceContentViewCookieType, url: contentView.representedObject.url};
122         }
123
124         var cookie = {};
125
126         if (representedObject instanceof WebInspector.CookieStorageObject) {
127             cookie.type = WebInspector.ResourceSidebarPanel.CookieStorageContentViewCookieType;
128             cookie.host = representedObject.host;
129         } else if (representedObject instanceof WebInspector.DatabaseObject) {
130             cookie.type = WebInspector.ResourceSidebarPanel.DatabaseContentViewCookieType;
131             cookie.host = representedObject.host;
132             cookie.name = representedObject.name;
133         } else if (representedObject instanceof WebInspector.DatabaseTableObject) {
134             cookie.type = WebInspector.ResourceSidebarPanel.DatabaseTableContentViewCookieType;
135             cookie.host = representedObject.database.host;
136             cookie.database = representedObject.database.name;
137             cookie.name = representedObject.name;
138         } else if (representedObject instanceof WebInspector.DOMStorageObject) {
139             cookie.type = WebInspector.ResourceSidebarPanel.DOMStorageContentViewCookieType;
140             cookie.isLocalStorage = representedObject.isLocalStorage();
141             cookie.host = representedObject.host;
142         } else if (representedObject instanceof WebInspector.ApplicationCacheFrame) {
143             cookie.type = WebInspector.ResourceSidebarPanel.ApplicationCacheContentViewCookieType;
144             cookie.frame = representedObject.frame.url;
145             cookie.manifest = representedObject.manifest.manifestURL;
146         } else if (representedObject instanceof WebInspector.Script) {
147             // If the Script does not have a URL, then there is not much more we can do to make a cookie.
148             // The URL case is handled above, do nothing here to prevent triggering the "Unknown" error below.
149             console.assert(!representedObject.url);
150         } else {
151             console.error("Unknown represented object.");
152         }
153
154         return cookie;
155     },
156
157     showContentViewForCookie: function(contentViewCookie)
158     {
159         if (!contentViewCookie || !contentViewCookie.type)
160             return;
161
162         this._contentViewCookieToShowWhenAvailable = contentViewCookie;
163
164         if (contentViewCookie.type === WebInspector.ResourceSidebarPanel.ResourceContentViewCookieType) {
165             // We can't show anything until we have the main frame in the sidebar. Otherwise the path components in the navigation bar would be missing.
166             if (!this._mainFrameTreeElement) {
167                 this._contentViewCookieToShowWhenAvailable = contentViewCookie;
168                 return;
169             }
170
171             var representedObject = contentViewCookie.url ? WebInspector.frameResourceManager.resourceForURL(contentViewCookie.url) : WebInspector.frameResourceManager.mainFrame;
172             if (!representedObject)
173                 representedObject = WebInspector.frameResourceManager.mainFrame;
174
175             if (!representedObject)
176                 return;
177
178             if (representedObject instanceof WebInspector.Resource && representedObject.isMainResource())
179                 representedObject = representedObject.parentFrame;
180
181             this.treeElementForRepresentedObject(representedObject).revealAndSelect(true, true);
182
183             return;
184         }
185
186         // It must be a storage cookie.
187
188         function finalizeCookieChecking()
189         {
190             // Walk all the tree elements and match them based on type alone. So if you were looking at cookies
191             // on one site, and later open the inspector on another site, the new site's cookies will show.
192             var currentTreeElement = this.contentTreeOutline.children[0];
193             while (currentTreeElement && !currentTreeElement.root) {
194                 if (this._checkStorageTreeElementAgainstPendingContentViewCookie(currentTreeElement, true))
195                     break;
196                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
197             }
198
199             delete this._contentViewCookieToShowWhenAvailable;
200             delete this._finalizeCookieCheckingTimeout;
201         }
202
203         if (this._finalizeCookieCheckingTimeout)
204             clearTimeout(this._finalizeCookieCheckingTimeout);
205
206         // When the specific storage item wasn't found we want to relax the check to show the first item with the
207         // same type. There is no good time to naturally declare the cookie wasn't found, so we do that on a timeout.
208         this._finalizeCookieCheckingTimeout = setTimeout(finalizeCookieChecking.bind(this), 500);
209     },
210
211     showMainFrameDOMTree: function(nodeToSelect, preventFocusChange)
212     {
213         var contentView = WebInspector.contentBrowser.contentViewForRepresentedObject(WebInspector.frameResourceManager.mainFrame);
214         contentView.showDOMTree(nodeToSelect, preventFocusChange);
215         WebInspector.contentBrowser.showContentView(contentView);
216     },
217
218     showMainFrameSourceCode: function()
219     {
220         var contentView = WebInspector.contentBrowser.contentViewForRepresentedObject(WebInspector.frameResourceManager.mainFrame);
221         contentView.showSourceCode();
222         WebInspector.contentBrowser.showContentView(contentView);
223     },
224
225     showSourceCodeForFrame: function(frameIdentifier, revealAndSelectTreeElement)
226     {
227         delete this._frameIdentifierToShowSourceCodeWhenAvailable;
228
229         // We can't show anything until we have the main frame in the sidebar.
230         // Otherwise the path components in the navigation bar would be missing.
231         var frame = WebInspector.frameResourceManager.frameForIdentifier(frameIdentifier);
232         if (!frame || !this._mainFrameTreeElement) {
233             this._frameIdentifierToShowSourceCodeWhenAvailable = frameIdentifier;
234             return;
235         }
236
237         var contentView = WebInspector.contentBrowser.contentViewForRepresentedObject(frame);
238         console.assert(contentView);
239         if (!contentView)
240             return;
241
242         contentView.showSourceCode();
243         WebInspector.contentBrowser.showContentView(contentView);
244
245         if (revealAndSelectTreeElement)
246             this.treeElementForRepresentedObject(frame).revealAndSelect(true, true, true, true);
247     },
248
249     showSourceCode: function(sourceCode, positionToReveal, textRangeToSelect, forceUnformatted)
250     {
251         console.assert(!positionToReveal || positionToReveal instanceof WebInspector.SourceCodePosition, positionToReveal);
252         var representedObject = sourceCode;
253
254         if (representedObject instanceof WebInspector.Script) {
255             // A script represented by a resource should always show the resource.
256             representedObject = representedObject.resource || representedObject;
257         }
258
259         // A main resource is always represented by its parent frame.
260         if (representedObject instanceof WebInspector.Resource && representedObject.isMainResource())
261             representedObject = representedObject.parentFrame;
262
263         var newContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(representedObject);
264         var cookie = {lineNumber: positionToReveal.lineNumber, columnNumber: positionToReveal.columnNumber};
265
266         var restoreCallback = function(contentView, savedCookie) {
267             var lineNumber = savedCookie.lineNumber;
268             var columnNumber = savedCookie.columnNumber;
269             var position = new WebInspector.SourceCodePosition(lineNumber, columnNumber);
270
271             if (contentView instanceof WebInspector.FrameContentView)
272                 contentView.showSourceCode(position)
273             else if (contentView instanceof WebInspector.ResourceClusterContentView)
274                 contentView.showResponse(position)
275             else if (contentView instanceof WebInspector.ScriptContentView)
276                 contentView.revealPosition(position)
277         };
278
279         WebInspector.contentBrowser.showContentView(newContentView, cookie, restoreCallback);
280     },
281
282     showSourceCodeLocation: function(sourceCodeLocation)
283     {
284         this.showSourceCode(sourceCodeLocation.displaySourceCode, sourceCodeLocation.displayPosition());
285     },
286
287     showOriginalUnformattedSourceCodeLocation: function(sourceCodeLocation)
288     {
289         this.showSourceCode(sourceCodeLocation.sourceCode, sourceCodeLocation.position(), undefined, true);
290     },
291
292     showOriginalOrFormattedSourceCodeLocation: function(sourceCodeLocation)
293     {
294         this.showSourceCode(sourceCodeLocation.sourceCode, sourceCodeLocation.formattedPosition());
295     },
296
297     showSourceCodeTextRange: function(sourceCodeTextRange)
298     {
299         var textRangeToSelect = sourceCodeTextRange.displayTextRange;
300         this.showSourceCode(sourceCodeTextRange.displaySourceCode, textRangeToSelect.startPosition(), textRangeToSelect);
301     },
302
303     showOriginalOrFormattedSourceCodeTextRange: function(sourceCodeTextRange)
304     {
305         var textRangeToSelect = sourceCodeTextRange.formattedTextRange;
306         this.showSourceCode(sourceCodeTextRange.sourceCode, textRangeToSelect.startPosition(), textRangeToSelect);
307     },
308
309     showResource: function(resource)
310     {
311         WebInspector.contentBrowser.showContentViewForRepresentedObject(resource.isMainResource() ? resource.parentFrame : resource);
312     },
313
314     showResourceRequest: function(resource)
315     {
316         var contentView = WebInspector.contentBrowser.contentViewForRepresentedObject(resource.isMainResource() ? resource.parentFrame : resource);
317
318         if (contentView instanceof WebInspector.FrameContentView)
319             var resourceContentView = contentView.showResource();
320         else if (contentView instanceof WebInspector.ResourceClusterContentView)
321             var resourceContentView = contentView;
322
323         console.assert(resourceContentView instanceof WebInspector.ResourceClusterContentView);
324         if (!(resourceContentView instanceof WebInspector.ResourceClusterContentView))
325             return;
326
327         resourceContentView.showRequest();
328
329         WebInspector.contentBrowser.showContentView(contentView);
330     },
331
332     treeElementForRepresentedObject: function(representedObject)
333     {
334         // A custom implementation is needed for this since the frames are populated lazily.
335
336         function isAncestor(ancestor, resourceOrFrame)
337         {
338             // SourceMapResources are descendants of another SourceCode object.
339             if (resourceOrFrame instanceof WebInspector.SourceMapResource) {
340                 if (resourceOrFrame.sourceMap.originalSourceCode === ancestor)
341                     return true;
342
343                 // Not a direct ancestor, so check the ancestors of the parent SourceCode object.
344                 resourceOrFrame = resourceOrFrame.sourceMap.originalSourceCode;
345             }
346
347             var currentFrame = resourceOrFrame.parentFrame;
348             while (currentFrame) {
349                 if (currentFrame === ancestor)
350                     return true;
351                 currentFrame = currentFrame.parentFrame;
352             }
353
354             return false;
355         }
356
357         function getParent(resourceOrFrame)
358         {
359             // SourceMapResources are descendants of another SourceCode object.
360             if (resourceOrFrame instanceof WebInspector.SourceMapResource)
361                 return resourceOrFrame.sourceMap.originalSourceCode;
362             return resourceOrFrame.parentFrame;
363         }
364
365         var treeElement = this._resourcesContentTreeOutline.findTreeElement(representedObject, isAncestor, getParent);
366         if (treeElement)
367             return treeElement;
368
369         // Only special case Script objects.
370         if (!(representedObject instanceof WebInspector.Script)) {
371             console.error("Didn't find a TreeElement for a representedObject associated with the ResourceSidebarPanel.");
372             return null;
373         }
374
375         // If the Script has a URL we should have found it earlier.
376         if (representedObject.url) {
377             console.error("Didn't find a ScriptTreeElement for a Script with a URL.");
378             return null;
379         }
380
381         // Since the Script does not have a URL we consider it an 'anonymous' script. These scripts happen from calls to
382         // window.eval() or browser features like Auto Fill and Reader. They are not normally added to the sidebar, but since
383         // we have a ScriptContentView asking for the tree element we will make a ScriptTreeElement on demand and add it.
384
385         if (!this._anonymousScriptsFolderTreeElement)
386             this._anonymousScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Anonymous Scripts"));
387
388         if (!this._anonymousScriptsFolderTreeElement.parent) {
389             var index = insertionIndexForObjectInListSortedByFunction(this._anonymousScriptsFolderTreeElement, this._resourcesContentTreeOutline.children, this._compareTreeElements);
390             this._resourcesContentTreeOutline.insertChild(this._anonymousScriptsFolderTreeElement, index);
391         }
392
393         var scriptTreeElement = new WebInspector.ScriptTreeElement(representedObject);
394         this._anonymousScriptsFolderTreeElement.appendChild(scriptTreeElement);
395
396         return scriptTreeElement;
397     },
398
399     performSearch: function(searchTerm)
400     {
401         // Before performing a new search, clear the old search.
402         this._searchContentTreeOutline.removeChildren();
403
404         this._inputElement.value = searchTerm;
405         this._searchQuerySetting.value = searchTerm;
406         this._lastSearchedPageSetting.value = searchTerm && WebInspector.frameResourceManager.mainFrame ? WebInspector.frameResourceManager.mainFrame.url.hash : null;
407
408         this.hideEmptyContentPlaceholder();
409
410         searchTerm = searchTerm.trim();
411         if (!searchTerm.length) {
412             this.filterBar.placeholder = WebInspector.UIString("Filter Resource List");
413             this.contentTreeOutline = this._resourcesContentTreeOutline;
414             return;
415         }
416
417         this.filterBar.placeholder = WebInspector.UIString("Filter Search Results");
418         this.contentTreeOutline = this._searchContentTreeOutline;
419
420         var updateEmptyContentPlaceholderTimeout = null;
421
422         function updateEmptyContentPlaceholderSoon()
423         {
424             if (updateEmptyContentPlaceholderTimeout)
425                 return;
426             updateEmptyContentPlaceholderTimeout = setTimeout(updateEmptyContentPlaceholder.bind(this), 100);
427         }
428
429         function updateEmptyContentPlaceholder()
430         {
431             if (updateEmptyContentPlaceholderTimeout) {
432                 clearTimeout(updateEmptyContentPlaceholderTimeout);
433                 updateEmptyContentPlaceholderTimeout = null;
434             }
435
436             this.updateEmptyContentPlaceholder(WebInspector.UIString("No Search Results"));
437         }
438
439         function resourcesCallback(error, result)
440         {
441             updateEmptyContentPlaceholderSoon.call(this);
442
443             if (error)
444                 return;
445
446             for (var i = 0; i < result.length; ++i) {
447                 var searchResult = result[i];
448                 if (!searchResult.url || !searchResult.frameId)
449                     continue;
450
451                 function resourceCallback(url, error, resourceMatches)
452                 {
453                     updateEmptyContentPlaceholderSoon.call(this);
454
455                     if (error || !resourceMatches || !resourceMatches.length)
456                         return;
457
458                     var frame = WebInspector.frameResourceManager.frameForIdentifier(searchResult.frameId);
459                     if (!frame)
460                         return;
461
462                     var resource = frame.url === url ? frame.mainResource : frame.resourceForURL(url);
463                     if (!resource)
464                         return;
465
466                     var resourceTreeElement = this._searchTreeElementForResource(resource);
467
468                     for (var i = 0; i < resourceMatches.length; ++i) {
469                         var match = resourceMatches[i];
470
471                         var lineMatch;
472                         var searchRegex = new RegExp(searchTerm.escapeForRegExp(), "gi");
473                         while ((searchRegex.lastIndex < match.lineContent.length) && (lineMatch = searchRegex.exec(match.lineContent))) {
474                             var matchObject = new WebInspector.ResourceSearchMatchObject(resource, match.lineContent, searchTerm, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, searchRegex.lastIndex));
475                             var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
476                             resourceTreeElement.appendChild(matchTreeElement);
477                         }
478                     }
479
480                     updateEmptyContentPlaceholder.call(this);
481                 }
482
483                 PageAgent.searchInResource(searchResult.frameId, searchResult.url, searchTerm, false, false, resourceCallback.bind(this, searchResult.url));
484             }
485         }
486
487         function domCallback(error, searchId, resultsCount)
488         {
489             updateEmptyContentPlaceholderSoon.call(this);
490
491             if (error || !resultsCount)
492                 return;
493
494             this._domSearchIdentifier = searchId;
495
496             function domSearchResults(error, nodeIds)
497             {
498                 updateEmptyContentPlaceholderSoon.call(this);
499
500                 if (error)
501                     return;
502
503                 for (var i = 0; i < nodeIds.length; ++i) {
504                     // If someone started a new search, then return early and stop showing seach results from the old query.
505                     if (this._domSearchIdentifier !== searchId)
506                         return;
507
508                     var domNode = WebInspector.domTreeManager.nodeForId(nodeIds[i]);
509                     if (!domNode || !domNode.ownerDocument)
510                         continue;
511
512                     // 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.
513                     if (domNode.nodeType() === Node.DOCUMENT_NODE)
514                         continue;
515
516                     // FIXME: Use this should use a frame to do resourceForURL, but DOMAgent does not provide a frameId.
517                     var resource = WebInspector.frameResourceManager.resourceForURL(domNode.ownerDocument.documentURL);
518                     if (!resource)
519                         continue;
520
521                     var resourceTreeElement = this._searchTreeElementForResource(resource);
522
523                     var domNodeTitle = WebInspector.DOMSearchMatchObject.titleForDOMNode(domNode);
524                     var searchRegex = new RegExp(searchTerm.escapeForRegExp(), "gi");
525
526                     // Textual matches.
527                     var lineMatch;
528                     var didFindTextualMatch = false;
529                     while ((searchRegex.lastIndex < domNodeTitle.length) && (lineMatch = searchRegex.exec(domNodeTitle))) {
530                         var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, searchTerm, new WebInspector.TextRange(0, lineMatch.index, 0, searchRegex.lastIndex));
531                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
532                         resourceTreeElement.appendChild(matchTreeElement);
533                         didFindTextualMatch = true;
534                     }
535
536                     // Non-textual matches are CSS Selector or XPath matches. In such cases, display the node entirely highlighted.
537                     if (!didFindTextualMatch) {
538                         var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, domNodeTitle, new WebInspector.TextRange(0, 0, 0, domNodeTitle.length));
539                         var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject);
540                         resourceTreeElement.appendChild(matchTreeElement);
541                     }
542
543                     updateEmptyContentPlaceholder.call(this);
544                 }
545             }
546
547             DOMAgent.getSearchResults(searchId, 0, resultsCount, domSearchResults.bind(this));
548         }
549
550         WebInspector.domTreeManager.requestDocument();
551
552         // FIXME: Should we be searching for regexes or just plain text?
553         PageAgent.searchInResources(searchTerm, false, false, resourcesCallback.bind(this));
554
555         if ("_domSearchIdentifier" in this) {
556             DOMAgent.discardSearchResults(this._domSearchIdentifier);
557             delete this._domSearchIdentifier;
558         }
559
560         DOMAgent.performSearch(searchTerm, domCallback.bind(this));
561     },
562
563     // Private
564
565     _searchFieldChanged: function(event)
566     {
567         this.performSearch(event.target.value);
568     },
569
570     _searchFieldInput: function(event)
571     {
572         // If the search field is cleared, immediately clear the search results tree outline.
573         if (!event.target.value.length && this.contentTreeOutline === this._searchContentTreeOutline)
574             this.performSearch("");
575     },
576
577     _searchTreeElementForResource: function(resource)
578     {
579         // FIXME: This should take a frame ID (if one is available) - so we can differentiate between multiple resources
580         // with the same URL.
581
582         var resourceTreeElement = this._searchContentTreeOutline.getCachedTreeElement(resource);
583         if (!resourceTreeElement) {
584             resourceTreeElement = new WebInspector.ResourceTreeElement(resource);
585             resourceTreeElement.hasChildren = true;
586             resourceTreeElement.expand();
587
588             this._searchContentTreeOutline.appendChild(resourceTreeElement);
589         }
590
591         return resourceTreeElement;
592     },
593
594     _focusSearchField: function(keyboardShortcut, event)
595     {
596         this.show();
597
598         this._inputElement.select();
599     },
600
601     _mainFrameDidChange: function(event)
602     {
603         if (this._mainFrameTreeElement) {
604             this._mainFrameTreeElement.frame.removeEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
605             this._resourcesContentTreeOutline.removeChild(this._mainFrameTreeElement);
606             this._mainFrameTreeElement = null;
607         }
608
609         var newFrame = WebInspector.frameResourceManager.mainFrame;
610         if (newFrame) {
611             newFrame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
612             this._mainFrameTreeElement = new WebInspector.FrameTreeElement(newFrame);
613             this._resourcesContentTreeOutline.insertChild(this._mainFrameTreeElement, 0);
614
615             // Select by default. Allow onselect if we aren't showing a content view.
616             if (!this._resourcesContentTreeOutline.selectedTreeElement)
617                 this._mainFrameTreeElement.revealAndSelect(true, false, !!WebInspector.contentBrowser.currentContentView);
618
619             if (this._frameIdentifierToShowSourceCodeWhenAvailable)
620                 this.showSourceCodeForFrame(this._frameIdentifierToShowSourceCodeWhenAvailable, true);
621             else if (this._contentViewCookieToShowWhenAvailable) {
622                 this.showContentViewForCookie(this._contentViewCookieToShowWhenAvailable);
623
624                 // The cookie is only useful until the main frame loads.
625                 delete this._contentViewCookieToShowWhenAvailable;
626             }
627         }
628
629         // We only care about the first time the main frame changes.
630         if (!this._waitingForInitialMainFrame)
631             return;
632
633         // Only if there is a main frame.
634         if (!newFrame)
635             return;
636
637         delete this._waitingForInitialMainFrame;
638
639         // Only if the last page searched is the same as the current page.
640         if (this._lastSearchedPageSetting.value !== newFrame.url.hash)
641             return;
642
643         // Search for whatever is in the input field. This was populated with the last used search term.
644         this.performSearch(this._inputElement.value);
645     },
646
647     _mainFrameMainResourceDidChange: function(event)
648     {
649         var currentContentView = WebInspector.contentBrowser.currentContentView;
650         var wasShowingResourceContentView = currentContentView instanceof WebInspector.ResourceContentView
651             || currentContentView instanceof WebInspector.FrameContentView || currentContentView instanceof WebInspector.ScriptContentView;
652
653         // Close all resource and frame content views since the main frame has navigated and all resources are cleared.
654         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.ResourceClusterContentView);
655         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.FrameContentView);
656         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.ScriptContentView);
657
658         function delayedWork()
659         {
660             // Show the main frame since there is no content view showing or we were showing a resource before.
661             // FIXME: We could try to select the same resource that was selected before in the case of a reload.
662             if (!WebInspector.contentBrowser.currentContentView || wasShowingResourceContentView)
663                 this._mainFrameTreeElement.revealAndSelect(true, false);
664         }
665
666         // Delay this work because other listeners of this event might not have fired yet. So selecting the main frame
667         // before those listeners do their work might cause the content of the old page to show instead of the new page.
668         setTimeout(delayedWork.bind(this), 0);
669     },
670
671     _frameWasAdded: function(event)
672     {
673         if (!this._frameIdentifierToShowSourceCodeWhenAvailable)
674             return;
675
676         var frame = event.data.frame;
677         if (frame.id !== this._frameIdentifierToShowSourceCodeWhenAvailable)
678             return;
679
680         this.showSourceCodeForFrame(frame.id, true);
681     },
682
683     _scriptWasAdded: function(event)
684     {
685         var script = event.data.script;
686
687         // We don't add scripts without URLs here. Those scripts can quickly clutter the interface and
688         // are usually more transient. They will get added if/when they need to be shown in a content view.
689         if (!script.url)
690             return;
691
692         // Exclude inspector scripts.
693         if (script.url.indexOf("__WebInspector") === 0)
694             return;
695
696         // If the script URL matches a resource we can assume it is part of that resource and does not need added.
697         if (script.resource)
698             return;
699
700         if (script.injected) {
701             if (!this._extensionScriptsFolderTreeElement)
702                 this._extensionScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extension Scripts"));
703             var parentFolderTreeElement = this._extensionScriptsFolderTreeElement;
704         } else {
705             if (!this._extraScriptsFolderTreeElement)
706                 this._extraScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extra Scripts"));
707             var parentFolderTreeElement = this._extraScriptsFolderTreeElement;
708         }
709
710         if (!parentFolderTreeElement.parent) {
711             var index = insertionIndexForObjectInListSortedByFunction(parentFolderTreeElement, this._resourcesContentTreeOutline.children, this._compareTreeElements);
712             this._resourcesContentTreeOutline.insertChild(parentFolderTreeElement, index);
713         }
714
715         var scriptTreeElement = new WebInspector.ScriptTreeElement(script);
716         parentFolderTreeElement.appendChild(scriptTreeElement);
717     },
718
719     _scriptsCleared: function(event)
720     {
721         if (this._extensionScriptsFolderTreeElement) {
722             if (this._extensionScriptsFolderTreeElement.parent)
723                 this._extensionScriptsFolderTreeElement.parent.removeChild(this._extensionScriptsFolderTreeElement);
724             this._extensionScriptsFolderTreeElement = null;
725         }
726
727         if (this._extraScriptsFolderTreeElement) {
728             if (this._extraScriptsFolderTreeElement.parent)
729                 this._extraScriptsFolderTreeElement.parent.removeChild(this._extraScriptsFolderTreeElement);
730             this._extraScriptsFolderTreeElement = null;
731         }
732
733         if (this._anonymousScriptsFolderTreeElement) {
734             if (this._anonymousScriptsFolderTreeElement.parent)
735                 this._anonymousScriptsFolderTreeElement.parent.removeChild(this._anonymousScriptsFolderTreeElement);
736             this._anonymousScriptsFolderTreeElement = null;
737         }
738     },
739
740     _treeElementSelected: function(treeElement, selectedByUser)
741     {
742         if (treeElement instanceof WebInspector.FolderTreeElement)
743             return;
744
745         if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement ||
746             treeElement instanceof WebInspector.StorageTreeElement || treeElement instanceof WebInspector.DatabaseTableTreeElement ||
747             treeElement instanceof WebInspector.DatabaseTreeElement || treeElement instanceof WebInspector.ApplicationCacheFrameTreeElement) {
748             WebInspector.contentBrowser.showContentViewForRepresentedObject(treeElement.representedObject);
749             return;
750         }
751
752         console.assert(treeElement instanceof WebInspector.SearchResultTreeElement);
753         if (!(treeElement instanceof WebInspector.SearchResultTreeElement))
754             return;
755
756         if (treeElement.representedObject instanceof WebInspector.DOMSearchMatchObject)
757             this.showMainFrameDOMTree(treeElement.representedObject.domNode, true);
758         else if (treeElement.representedObject instanceof WebInspector.ResourceSearchMatchObject)
759             this.showOriginalOrFormattedSourceCodeTextRange(treeElement.representedObject.sourceCodeTextRange);
760     },
761
762     _domNodeWasInspected: function(event)
763     {
764         this.showMainFrameDOMTree(event.data.node);
765     },
766
767     _checkStorageTreeElementAgainstPendingContentViewCookie: function(treeElement, matchOnTypeAlone)
768     {
769         var contentViewCookie = this._contentViewCookieToShowWhenAvailable;
770         if (!contentViewCookie || !contentViewCookie.type)
771             return false;
772
773         if (contentViewCookie.type === WebInspector.ResourceSidebarPanel.ResourceContentViewCookieType)
774             return;
775
776         var representedObject = treeElement.representedObject;
777
778         switch (contentViewCookie.type) {
779         case WebInspector.ResourceSidebarPanel.CookieStorageContentViewCookieType:
780             if (!(representedObject instanceof WebInspector.CookieStorageObject))
781                 return;
782             if (!matchOnTypeAlone && representedObject.host !== contentViewCookie.host)
783                 return false;
784             break;
785
786         case WebInspector.ResourceSidebarPanel.DatabaseContentViewCookieType:
787             if (!(representedObject instanceof WebInspector.DatabaseObject))
788                 return false;
789             if (!matchOnTypeAlone && representedObject.host !== contentViewCookie.host)
790                 return false;
791             if (!matchOnTypeAlone && representedObject.name !== contentViewCookie.name)
792                 return false;
793             break;
794
795         case WebInspector.ResourceSidebarPanel.DatabaseTableContentViewCookieType:
796             // FIXME: This isn't easy to implement like the others since DatabaseTreeElement
797             // populates the tables instead of ResourceSidebarPanel. Just select the database.
798             if (!(representedObject instanceof WebInspector.DatabaseObject))
799                 return false;
800             if (!matchOnTypeAlone && representedObject.host !== contentViewCookie.host)
801                 return false;
802             if (!matchOnTypeAlone && representedObject.name !== contentViewCookie.database)
803                 return false;
804             return;
805
806         case WebInspector.ResourceSidebarPanel.DOMStorageContentViewCookieType:
807             if (!(representedObject instanceof WebInspector.DOMStorageObject))
808                 return false;
809             if (!matchOnTypeAlone && representedObject.host !== contentViewCookie.host)
810                 return false;
811             if (!matchOnTypeAlone && representedObject.isLocalStorage() !== contentViewCookie.isLocalStorage)
812                 return false;
813             break;
814
815         case WebInspector.ResourceSidebarPanel.ApplicationCacheContentViewCookieType:
816             if (!(representedObject instanceof WebInspector.ApplicationCacheFrame))
817                 return;
818             if (!matchOnTypeAlone && representedObject.frame.url !== contentViewCookie.frame)
819                 return;
820             if (!matchOnTypeAlone && representedObject.manifest.manifestURL !== contentViewCookie.manifest)
821                 return false;
822             break;
823
824         default:
825             console.assert("Unknown content view cookie type.");
826             return false;
827         }
828
829         // If we got here, then the tree element was a match to the content view cookie.
830         // Selecting the tree element will cause the content view to show.
831         treeElement.revealAndSelect(true, true);
832
833         return true;
834     },
835
836     _domStorageObjectWasAdded: function(event)
837     {
838         var domStorage = event.data.domStorage;
839         var storageElement = new WebInspector.DOMStorageTreeElement(domStorage);
840
841         if (domStorage.isLocalStorage())
842             this._localStorageRootTreeElement = this._addStorageChild(storageElement, this._localStorageRootTreeElement, WebInspector.UIString("Local Storage"));
843         else
844             this._sessionStorageRootTreeElement = this._addStorageChild(storageElement, this._sessionStorageRootTreeElement, WebInspector.UIString("Session Storage"));
845
846         this._checkStorageTreeElementAgainstPendingContentViewCookie(storageElement);
847     },
848
849     _domStorageObjectWasInspected: function(event)
850     {
851         var domStorage = event.data.domStorage;
852         var treeElement = this.treeElementForRepresentedObject(domStorage);
853         treeElement.revealAndSelect(true);
854     },
855
856     _databaseWasAdded: function(event)
857     {
858         var database = event.data.database;
859
860         console.assert(database instanceof WebInspector.DatabaseObject);
861
862         if (!this._databaseHostTreeElementMap[database.host]) {
863             this._databaseHostTreeElementMap[database.host] = new WebInspector.DatabaseHostTreeElement(database.host);
864             this._databaseRootTreeElement = this._addStorageChild(this._databaseHostTreeElementMap[database.host], this._databaseRootTreeElement, WebInspector.UIString("Databases"));
865         }
866
867         var databaseElement = new WebInspector.DatabaseTreeElement(database);
868         this._databaseHostTreeElementMap[database.host].appendChild(databaseElement);
869
870         this._checkStorageTreeElementAgainstPendingContentViewCookie(databaseElement);
871     },
872
873     _databaseWasInspected: function(event)
874     {
875         var database = event.data.database;
876         var treeElement = this.treeElementForRepresentedObject(database);
877         treeElement.revealAndSelect(true);
878     },
879
880     _cookieStorageObjectWasAdded: function(event)
881     {
882         console.assert(event.data.cookieStorage instanceof WebInspector.CookieStorageObject);
883
884         var cookieElement = new WebInspector.CookieStorageTreeElement(event.data.cookieStorage);
885         this._cookieStorageRootTreeElement = this._addStorageChild(cookieElement, this._cookieStorageRootTreeElement, WebInspector.UIString("Cookies"));
886
887         this._checkStorageTreeElementAgainstPendingContentViewCookie(cookieElement);
888     },
889
890     _frameManifestAdded: function(event)
891     {
892         var frameManifest = event.data.frameManifest;
893         console.assert(frameManifest instanceof WebInspector.ApplicationCacheFrame);
894
895         var manifest = frameManifest.manifest;
896         var manifestURL = manifest.manifestURL;
897         if (!this._applicationCacheURLTreeElementMap[manifestURL]) {
898             this._applicationCacheURLTreeElementMap[manifestURL] = new WebInspector.ApplicationCacheManifestTreeElement(manifest);
899             this._applicationCacheRootTreeElement = this._addStorageChild(this._applicationCacheURLTreeElementMap[manifestURL], this._applicationCacheRootTreeElement, WebInspector.UIString("Application Cache"));
900         }
901
902         var frameCacheElement = new WebInspector.ApplicationCacheFrameTreeElement(frameManifest);
903         this._applicationCacheURLTreeElementMap[manifestURL].appendChild(frameCacheElement);
904
905         this._checkStorageTreeElementAgainstPendingContentViewCookie(frameCacheElement);
906     },
907
908     _frameManifestRemoved: function(event)
909     {
910          // FIXME: Implement this.
911     },
912
913     _compareTreeElements: function(a, b)
914     {
915         // Always sort the main frame element first.
916         if (a instanceof WebInspector.FrameTreeElement)
917             return -1;
918         if (b instanceof WebInspector.FrameTreeElement)
919             return 1;
920
921         console.assert(a.mainTitle);
922         console.assert(b.mainTitle);
923
924         return (a.mainTitle || "").localeCompare(b.mainTitle || "");
925     },
926
927     _addStorageChild: function(childElement, parentElement, folderName)
928     {
929         if (!parentElement) {
930             childElement.flattened = true;
931
932             this._resourcesContentTreeOutline.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, this._resourcesContentTreeOutline.children, this._compareTreeElements));
933
934             return childElement;
935         }
936
937         if (parentElement instanceof WebInspector.StorageTreeElement) {
938             console.assert(parentElement.flattened);
939
940             var previousOnlyChild = parentElement;
941             previousOnlyChild.flattened = false;
942             this._resourcesContentTreeOutline.removeChild(previousOnlyChild);
943
944             var folderElement = new WebInspector.FolderTreeElement(folderName, null, null);
945             this._resourcesContentTreeOutline.insertChild(folderElement, insertionIndexForObjectInListSortedByFunction(folderElement, this._resourcesContentTreeOutline.children, this._compareTreeElements));
946
947             folderElement.appendChild(previousOnlyChild);
948             folderElement.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, folderElement.children, this._compareTreeElements));
949
950             return folderElement;
951         }
952
953         console.assert(parentElement instanceof WebInspector.FolderTreeElement);
954         parentElement.insertChild(childElement, insertionIndexForObjectInListSortedByFunction(childElement, parentElement.children, this._compareTreeElements));
955
956         return parentElement;
957     },
958
959     _storageCleared: function(event)
960     {
961         // Close all DOM and cookie storage content views since the main frame has navigated and all storages are cleared.
962         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.CookieStorageContentView);
963         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DOMStorageContentView);
964         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DatabaseTableContentView);
965         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.DatabaseContentView);
966         WebInspector.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.ApplicationCacheFrameContentView);
967
968         if (this._localStorageRootTreeElement && this._localStorageRootTreeElement.parent)
969             this._localStorageRootTreeElement.parent.removeChild(this._localStorageRootTreeElement);
970
971         if (this._sessionStorageRootTreeElement && this._sessionStorageRootTreeElement.parent)
972             this._sessionStorageRootTreeElement.parent.removeChild(this._sessionStorageRootTreeElement);
973
974         if (this._databaseRootTreeElement && this._databaseRootTreeElement.parent)
975             this._databaseRootTreeElement.parent.removeChild(this._databaseRootTreeElement);
976
977         if (this._cookieStorageRootTreeElement && this._cookieStorageRootTreeElement.parent)
978             this._cookieStorageRootTreeElement.parent.removeChild(this._cookieStorageRootTreeElement);
979
980         if (this._applicationCacheRootTreeElement && this._applicationCacheRootTreeElement.parent)
981             this._applicationCacheRootTreeElement.parent.removeChild(this._applicationCacheRootTreeElement);
982
983         this._localStorageRootTreeElement = null;
984         this._sessionStorageRootTreeElement = null;
985         this._databaseRootTreeElement = null;
986         this._databaseHostTreeElementMap = {};
987         this._cookieStorageRootTreeElement = null;
988         this._applicationCacheRootTreeElement = null;
989         this._applicationCacheURLTreeElementMap = {};
990     }
991 };
992
993 WebInspector.ResourceSidebarPanel.prototype.__proto__ = WebInspector.NavigationSidebarPanel.prototype;