Web Inspector: decouple child element folderization logic from FrameTreeElement
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / FrameTreeElement.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.FrameTreeElement = function(frame, representedObject)
27 {
28     console.assert(frame instanceof WebInspector.Frame);
29
30     WebInspector.ResourceTreeElement.call(this, frame.mainResource, representedObject || frame);
31
32     this._frame = frame;
33
34     this._updateExpandedSetting();
35
36     frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
37     frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
38     frame.addEventListener(WebInspector.Frame.Event.ResourceWasRemoved, this._resourceWasRemoved, this);
39     frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasAdded, this._childFrameWasAdded, this);
40     frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
41
42     frame.domTree.addEventListener(WebInspector.DOMTree.Event.ContentFlowWasAdded, this._childContentFlowWasAdded, this);
43     frame.domTree.addEventListener(WebInspector.DOMTree.Event.ContentFlowWasRemoved, this._childContentFlowWasRemoved, this);
44     frame.domTree.addEventListener(WebInspector.DOMTree.Event.RootDOMNodeInvalidated, this._rootDOMNodeInvalidated, this);
45
46     if (this._frame.isMainFrame())
47         this._downloadingPage = false;
48
49     this.shouldRefreshChildren = true;
50     this.folderSettingsKey = this._frame.url.hash;
51
52     this.registerFolderizeSettings("frames", WebInspector.UIString("Frames"),
53         function(representedObject) { return representedObject instanceof WebInspector.Frame; },
54         function() { return this.frame.childFrames.length; }.bind(this),
55         WebInspector.FrameTreeElement
56     );
57
58     this.registerFolderizeSettings("flows", WebInspector.UIString("Flows"),
59         function(representedObject) { return representedObject instanceof WebInspector.ContentFlow; },
60         function() { return this.frame.domTree.flowsCount; }.bind(this),
61         WebInspector.ContentFlowTreeElement
62     );
63
64     function makeValidateCallback(resourceType) {
65         return function(representedObject) {
66             return representedObject instanceof WebInspector.Resource && representedObject.type === resourceType;
67         };
68     }
69
70     function makeChildCountCallback(frame, resourceType) {
71         return function() {
72             return frame.resourcesWithType(resourceType).length;
73         };
74     }
75
76     for (var key in WebInspector.Resource.Type) {
77         var value = WebInspector.Resource.Type[key];
78         var folderName = WebInspector.Resource.displayNameForType(value, true);
79         this.registerFolderizeSettings(key, folderName,
80             makeValidateCallback(value),
81             makeChildCountCallback(this.frame, value),
82             WebInspector.ResourceTreeElement
83         );
84     }
85
86     this.updateParentStatus();
87 };
88
89 WebInspector.FrameTreeElement.prototype = {
90     constructor: WebInspector.FrameTreeElement,
91     __proto__: WebInspector.ResourceTreeElement.prototype,
92
93     // Public
94
95     get frame()
96     {
97         return this._frame;
98     },
99
100     descendantResourceTreeElementTypeDidChange: function(resourceTreeElement, oldType)
101     {
102         // Called by descendant ResourceTreeElements.
103
104         // Add the tree element again, which will move it to the new location
105         // based on sorting and possible folder changes.
106         this._addTreeElement(resourceTreeElement);
107     },
108
109     descendantResourceTreeElementMainTitleDidChange: function(resourceTreeElement, oldMainTitle)
110     {
111         // Called by descendant ResourceTreeElements.
112
113         // Add the tree element again, which will move it to the new location
114         // based on sorting and possible folder changes.
115         this._addTreeElement(resourceTreeElement);
116     },
117
118     // Overrides from SourceCodeTreeElement.
119
120     updateSourceMapResources: function()
121     {
122         // Frames handle their own SourceMapResources.
123
124         if (!this.treeOutline || !this.treeOutline.includeSourceMapResourceChildren)
125             return;
126
127         if (!this._frame)
128             return;
129
130         this.updateParentStatus();
131
132         if (this.resource && this.resource.sourceMaps.length)
133             this.shouldRefreshChildren = true;
134     },
135
136     onattach: function()
137     {
138         // Immediate superclasses are skipped, since Frames handle their own SourceMapResources.
139         WebInspector.GeneralTreeElement.prototype.onattach.call(this);
140
141         if (this._frame.isMainFrame()) {
142             WebInspector.notifications.addEventListener(WebInspector.Notification.PageArchiveStarted, this._pageArchiveStarted, this);
143             WebInspector.notifications.addEventListener(WebInspector.Notification.PageArchiveEnded, this._pageArchiveEnded, this);
144         }
145     },
146
147     ondetach: function()
148     {
149         WebInspector.ResourceTreeElement.prototype.ondetach.call(this);
150
151         if (this._frame.isMainFrame()) {
152             WebInspector.notifications.removeEventListener(WebInspector.Notification.PageArchiveStarted, this._pageArchiveStarted, this);
153             WebInspector.notifications.removeEventListener(WebInspector.Notification.PageArchiveEnded, this._pageArchiveEnded, this);
154         }
155     },
156
157     // Overrides from FolderizedTreeElement (Protected).
158
159     compareChildTreeElements: function(a, b)
160     {
161         if (a === b)
162             return 0;
163
164         var aIsResource = a instanceof WebInspector.ResourceTreeElement;
165         var bIsResource = b instanceof WebInspector.ResourceTreeElement;
166
167         if (aIsResource && bIsResource)
168             return WebInspector.ResourceTreeElement.compareResourceTreeElements(a, b);
169
170         if (!aIsResource && !bIsResource) {
171             // When both components are not resources then default to base class comparison.
172             return WebInspector.ResourceTreeElement.prototype.compareChildTreeElements.call(this, a, b);
173         }
174
175         // Non-resources should appear before the resources.
176         // FIXME: There should be a better way to group the elements by their type.
177         return aIsResource ? 1 : -1;
178     },
179
180     // Called from ResourceTreeElement.
181
182     updateStatusForMainFrame: function()
183     {
184         function loadedImages()
185         {
186             if (!this._reloadButton || !this._downloadButton)
187                 return;
188
189             var fragment = document.createDocumentFragment("div");
190             fragment.appendChild(this._downloadButton.element);
191             fragment.appendChild(this._reloadButton.element);
192             this.status = fragment;
193
194             delete this._loadingMainFrameButtons;
195         }
196
197         if (this._reloadButton && this._downloadButton) {
198             loadedImages.call(this);
199             return;
200         }
201
202         if (!this._loadingMainFrameButtons) {
203             this._loadingMainFrameButtons = true;
204
205             var tooltip = WebInspector.UIString("Reload page (%s)\nReload ignoring cache (%s)").format(WebInspector._reloadPageKeyboardShortcut.displayName, WebInspector._reloadPageIgnoringCacheKeyboardShortcut.displayName);
206             wrappedSVGDocument(platformImagePath("Reload.svg"), null, tooltip, function(element) {
207                 this._reloadButton = new WebInspector.TreeElementStatusButton(element);
208                 this._reloadButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._reloadPageClicked, this);
209                 loadedImages.call(this);
210             }.bind(this));
211
212             wrappedSVGDocument(platformImagePath("DownloadArrow.svg"), null, WebInspector.UIString("Download Web Archive"), function(element) {
213                 this._downloadButton = new WebInspector.TreeElementStatusButton(element);
214                 this._downloadButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._downloadButtonClicked, this);
215                 this._updateDownloadButton();
216                 loadedImages.call(this);
217             }.bind(this));
218         }
219     },
220
221     // Overrides from TreeElement (Private).
222
223     onpopulate: function()
224     {
225         if (this.children.length && !this.shouldRefreshChildren)
226             return;
227
228         this.shouldRefreshChildren = false;
229
230         this.removeChildren();
231
232         for (var i = 0; i < this._frame.childFrames.length; ++i)
233             this.addChildForRepresentedObject(this._frame.childFrames[i]);
234
235         for (var i = 0; i < this._frame.resources.length; ++i)
236             this.addChildForRepresentedObject(this._frame.resources[i]);
237
238         var sourceMaps = this.resource && this.resource.sourceMaps;
239         for (var i = 0; i < sourceMaps.length; ++i) {
240             var sourceMap = sourceMaps[i];
241             for (var j = 0; j < sourceMap.resources.length; ++j)
242                 this.addChildForRepresentedObject(sourceMap.resources[j]);
243         }
244
245         var flowMap = this._frame.domTree.flowMap;
246         for (var flowKey in flowMap)
247             this.addChildForRepresentedObject(flowMap[flowKey]);
248     },
249
250     onexpand: function()
251     {
252         this._expandedSetting.value = true;
253         this._frame.domTree.requestContentFlowList();
254     },
255
256     oncollapse: function()
257     {
258         // Only store the setting if we have children, since setting hasChildren to false will cause a collapse,
259         // and we only care about user triggered collapses.
260         if (this.hasChildren)
261             this._expandedSetting.value = false;
262     },
263
264     // Private
265
266     _updateExpandedSetting: function()
267     {
268         this._expandedSetting = new WebInspector.Setting("frame-expanded-" + this._frame.url.hash, this._frame.isMainFrame() ? true : false);
269         if (this._expandedSetting.value)
270             this.expand();
271         else
272             this.collapse();
273     },
274
275     _mainResourceDidChange: function(event)
276     {
277         this._updateResource(this._frame.mainResource);
278
279         this.updateParentStatus();
280         this.removeChildren();
281
282         // Change the expanded setting since the frame URL has changed. Do this before setting shouldRefreshChildren, since
283         // shouldRefreshChildren will call onpopulate if expanded is true.
284         this._updateExpandedSetting();
285
286         if (this._frame.isMainFrame())
287             this._updateDownloadButton();
288
289         this.shouldRefreshChildren = true;
290     },
291
292     _resourceWasAdded: function(event)
293     {
294         this.addRepresentedObjectToNewChildQueue(event.data.resource);
295     },
296
297     _resourceWasRemoved: function(event)
298     {
299         this.removeChildForRepresentedObject(event.data.resource);
300     },
301
302     _childFrameWasAdded: function(event)
303     {
304         this.addRepresentedObjectToNewChildQueue(event.data.childFrame);
305     },
306
307     _childFrameWasRemoved: function(event)
308     {
309         this.removeChildForRepresentedObject(event.data.childFrame);
310     },
311
312     _childContentFlowWasAdded: function(event)
313     {
314         this.addRepresentedObjectToNewChildQueue(event.data.flow);
315     },
316
317     _childContentFlowWasRemoved: function(event)
318     {
319         this.removeChildForRepresentedObject(event.data.flow);
320     },
321
322     _rootDOMNodeInvalidated: function()
323     {
324         if (this.expanded)
325             this._frame.domTree.requestContentFlowList();
326     },
327
328     _reloadPageClicked: function(event)
329     {
330         // Ignore cache when the shift key is pressed.
331         PageAgent.reload(event.data.shiftKey);
332     },
333
334     _downloadButtonClicked: function(event)
335     {
336         WebInspector.archiveMainFrame();
337     },
338
339     _updateDownloadButton: function()
340     {
341         console.assert(this._frame.isMainFrame());
342         if (!this._downloadButton)
343             return;
344
345         if (!PageAgent.archive) {
346             this._downloadButton.hidden = true;
347             return;
348         }
349
350         if (this._downloadingPage) {
351             this._downloadButton.enabled = false;
352             return;
353         }
354
355         this._downloadButton.enabled = WebInspector.canArchiveMainFrame();
356     },
357
358     _pageArchiveStarted: function(event)
359     {
360         this._downloadingPage = true;
361         this._updateDownloadButton();
362     },
363
364     _pageArchiveEnded: function(event)
365     {
366         this._downloadingPage = false;
367         this._updateDownloadButton();
368     }
369 };