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