Web Inspector: Split Storage from Resources tab
[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);
31
32         this.contentBrowser = contentBrowser;
33
34         this.filterBar.placeholder = WebInspector.UIString("Filter Resource List");
35
36         this._waitingForInitialMainFrame = true;
37
38         WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
39
40         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptWasAdded, this);
41         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
42
43         WebInspector.notifications.addEventListener(WebInspector.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
44
45         this.contentTreeOutline.onselect = this._treeElementSelected.bind(this);
46         this.contentTreeOutline.includeSourceMapResourceChildren = true;
47
48         if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
49             this.contentTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
50     }
51
52     // Public
53
54     showDefaultContentView()
55     {
56         if (WebInspector.frameResourceManager.mainFrame) {
57             this.contentBrowser.showContentViewForRepresentedObject(WebInspector.frameResourceManager.mainFrame);
58             return;
59         }
60
61         var firstTreeElement = this.contentTreeOutline.children[0];
62         if (firstTreeElement)
63             this.showDefaultContentViewForTreeElement(firstTreeElement);
64     }
65
66     treeElementForRepresentedObject(representedObject)
67     {
68         // A custom implementation is needed for this since the frames are populated lazily.
69
70         if (!this._mainFrameTreeElement && (representedObject instanceof WebInspector.Resource || representedObject instanceof WebInspector.Frame)) {
71             // All resources are under the main frame, so we need to return early if we don't have the main frame yet.
72             return null;
73         }
74
75         // The Frame is used as the representedObject instead of the main resource in our tree.
76         if (representedObject instanceof WebInspector.Resource && representedObject.parentFrame && representedObject.parentFrame.mainResource === representedObject)
77             representedObject = representedObject.parentFrame;
78
79         function isAncestor(ancestor, resourceOrFrame)
80         {
81             // SourceMapResources are descendants of another SourceCode object.
82             if (resourceOrFrame instanceof WebInspector.SourceMapResource) {
83                 if (resourceOrFrame.sourceMap.originalSourceCode === ancestor)
84                     return true;
85
86                 // Not a direct ancestor, so check the ancestors of the parent SourceCode object.
87                 resourceOrFrame = resourceOrFrame.sourceMap.originalSourceCode;
88             }
89
90             var currentFrame = resourceOrFrame.parentFrame;
91             while (currentFrame) {
92                 if (currentFrame === ancestor)
93                     return true;
94                 currentFrame = currentFrame.parentFrame;
95             }
96
97             return false;
98         }
99
100         function getParent(resourceOrFrame)
101         {
102             // SourceMapResources are descendants of another SourceCode object.
103             if (resourceOrFrame instanceof WebInspector.SourceMapResource)
104                 return resourceOrFrame.sourceMap.originalSourceCode;
105             return resourceOrFrame.parentFrame;
106         }
107
108         var treeElement = this.contentTreeOutline.findTreeElement(representedObject, isAncestor, getParent);
109         if (treeElement)
110             return treeElement;
111
112         // Only special case Script objects.
113         if (!(representedObject instanceof WebInspector.Script))
114             return null;
115
116         // If the Script has a URL we should have found it earlier.
117         if (representedObject.url) {
118             console.error("Didn't find a ScriptTreeElement for a Script with a URL.");
119             return null;
120         }
121
122         // Since the Script does not have a URL we consider it an 'anonymous' script. These scripts happen from calls to
123         // window.eval() or browser features like Auto Fill and Reader. They are not normally added to the sidebar, but since
124         // we have a ScriptContentView asking for the tree element we will make a ScriptTreeElement on demand and add it.
125
126         if (!this._anonymousScriptsFolderTreeElement)
127             this._anonymousScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Anonymous Scripts"));
128
129         if (!this._anonymousScriptsFolderTreeElement.parent) {
130             var index = insertionIndexForObjectInListSortedByFunction(this._anonymousScriptsFolderTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
131             this.contentTreeOutline.insertChild(this._anonymousScriptsFolderTreeElement, index);
132         }
133
134         var scriptTreeElement = new WebInspector.ScriptTreeElement(representedObject);
135         this._anonymousScriptsFolderTreeElement.appendChild(scriptTreeElement);
136
137         return scriptTreeElement;
138     }
139
140     // Private
141
142     _mainFrameDidChange(event)
143     {
144         if (this._mainFrameTreeElement) {
145             this._mainFrameTreeElement.frame.removeEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
146             this.contentTreeOutline.removeChild(this._mainFrameTreeElement);
147             this._mainFrameTreeElement = null;
148         }
149
150         var newFrame = WebInspector.frameResourceManager.mainFrame;
151         if (newFrame) {
152             newFrame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainFrameMainResourceDidChange, this);
153             this._mainFrameTreeElement = new WebInspector.FrameTreeElement(newFrame);
154             this.contentTreeOutline.insertChild(this._mainFrameTreeElement, 0);
155
156             // Select a tree element by default. Allow onselect if we aren't showing a content view.
157             if (!this.contentTreeOutline.selectedTreeElement) {
158                 var currentContentView = this.contentBrowser.currentContentView;
159                 var treeElement = currentContentView ? this.treeElementForRepresentedObject(currentContentView.representedObject) : null;
160                 if (!treeElement)
161                     treeElement = this._mainFrameTreeElement;
162                 this.showDefaultContentViewForTreeElement(treeElement);
163             }
164         }
165
166         // The navigation path needs update when the main frame changes, since all resources are under the main frame.
167         this.contentBrowser.updateHierarchicalPathForCurrentContentView();
168
169         // We only care about the first time the main frame changes.
170         if (!this._waitingForInitialMainFrame)
171             return;
172
173         // Only if there is a main frame.
174         if (!newFrame)
175             return;
176
177         this._waitingForInitialMainFrame = false;
178     }
179
180     _mainFrameMainResourceDidChange(event)
181     {
182         var wasShowingResourceSidebar = this.selected;
183         var currentContentView = this.contentBrowser.currentContentView;
184         var wasShowingResourceContentView = currentContentView instanceof WebInspector.ResourceContentView
185             || currentContentView instanceof WebInspector.ResourceClusterContentView
186             || currentContentView instanceof WebInspector.FrameContentView
187             || currentContentView instanceof WebInspector.ScriptContentView;
188
189         this.contentBrowser.contentViewContainer.closeAllContentViews();
190
191         function delayedWork()
192         {
193             // Show the main frame since there is no content view showing or we were showing a resource before.
194             // Cookie restoration will attempt to re-select the resource we were showing.
195             if (!this.contentBrowser.currentContentView || wasShowingResourceContentView) {
196                 // NOTE: This selection, during provisional loading, won't be saved, so it is
197                 // safe to do and not-clobber cookie restoration.
198                 this.showDefaultContentViewForTreeElement(this._mainFrameTreeElement);
199             }
200         }
201
202         // Delay this work because other listeners of this event might not have fired yet. So selecting the main frame
203         // before those listeners do their work might cause the content of the old page to show instead of the new page.
204         setTimeout(delayedWork.bind(this), 0);
205     }
206
207     _scriptWasAdded(event)
208     {
209         var script = event.data.script;
210
211         // We don't add scripts without URLs here. Those scripts can quickly clutter the interface and
212         // are usually more transient. They will get added if/when they need to be shown in a content view.
213         if (!script.url)
214             return;
215
216         // Exclude inspector scripts.
217         if (script.url.startsWith("__WebInspector"))
218             return;
219
220         // If the script URL matches a resource we can assume it is part of that resource and does not need added.
221         if (script.resource)
222             return;
223
224         var insertIntoTopLevel = false;
225
226         if (script.injected) {
227             if (!this._extensionScriptsFolderTreeElement)
228                 this._extensionScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extension Scripts"));
229             var parentFolderTreeElement = this._extensionScriptsFolderTreeElement;
230         } else {
231             if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript && !WebInspector.hasExtraDomains)
232                 insertIntoTopLevel = true;
233             else {
234                 if (!this._extraScriptsFolderTreeElement)
235                     this._extraScriptsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Extra Scripts"));
236                 var parentFolderTreeElement = this._extraScriptsFolderTreeElement;
237             }
238         }
239
240         var scriptTreeElement = new WebInspector.ScriptTreeElement(script);
241
242         if (insertIntoTopLevel) {
243             var index = insertionIndexForObjectInListSortedByFunction(scriptTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
244             this.contentTreeOutline.insertChild(scriptTreeElement, index);
245         } else {
246             if (!parentFolderTreeElement.parent) {
247                 var index = insertionIndexForObjectInListSortedByFunction(parentFolderTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
248                 this.contentTreeOutline.insertChild(parentFolderTreeElement, index);
249             }
250
251             parentFolderTreeElement.appendChild(scriptTreeElement);
252         }
253     }
254
255     _scriptsCleared(event)
256     {
257         if (this._extensionScriptsFolderTreeElement) {
258             if (this._extensionScriptsFolderTreeElement.parent)
259                 this._extensionScriptsFolderTreeElement.parent.removeChild(this._extensionScriptsFolderTreeElement);
260             this._extensionScriptsFolderTreeElement = null;
261         }
262
263         if (this._extraScriptsFolderTreeElement) {
264             if (this._extraScriptsFolderTreeElement.parent)
265                 this._extraScriptsFolderTreeElement.parent.removeChild(this._extraScriptsFolderTreeElement);
266             this._extraScriptsFolderTreeElement = null;
267         }
268
269         if (this._anonymousScriptsFolderTreeElement) {
270             if (this._anonymousScriptsFolderTreeElement.parent)
271                 this._anonymousScriptsFolderTreeElement.parent.removeChild(this._anonymousScriptsFolderTreeElement);
272             this._anonymousScriptsFolderTreeElement = null;
273         }
274     }
275
276     _treeElementSelected(treeElement, selectedByUser)
277     {
278         if (treeElement instanceof WebInspector.FolderTreeElement)
279             return;
280
281         if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) {
282             WebInspector.showRepresentedObject(treeElement.representedObject);
283             return;
284         }
285
286         console.error("Unknown tree element", treeElement);
287     }
288
289     _compareTreeElements(a, b)
290     {
291         // Always sort the main frame element first.
292         if (a instanceof WebInspector.FrameTreeElement)
293             return -1;
294         if (b instanceof WebInspector.FrameTreeElement)
295             return 1;
296
297         console.assert(a.mainTitle);
298         console.assert(b.mainTitle);
299
300         return (a.mainTitle || "").localeCompare(b.mainTitle || "");
301     }
302
303     _extraDomainsActivated()
304     {
305         if (WebInspector.debuggableType === WebInspector.DebuggableType.JavaScript)
306             this.contentTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName);
307     }
308 };