Web Inspector: Instrument active pixel memory used by canvases
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / CanvasDetailsSidebarPanel.js
1 /*
2  * Copyright (C) 2017 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.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends WebInspector.DetailsSidebarPanel
27 {
28     constructor()
29     {
30         super("canvas-details", WebInspector.UIString("Canvas"));
31
32         this.element.classList.add("canvas");
33
34         this._canvas = null;
35         this._node = null;
36     }
37
38     // Public
39
40     inspect(objects)
41     {
42         if (!(objects instanceof Array))
43             objects = [objects];
44
45         this.canvas = objects.find((object) => object instanceof WebInspector.Canvas);
46
47         return !!this._canvas;
48     }
49
50     get canvas()
51     {
52         return this._canvas;
53     }
54
55     set canvas(canvas)
56     {
57         if (canvas === this._canvas)
58             return;
59
60         if (this._node) {
61             this._node.removeEventListener(WebInspector.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
62             this._node.removeEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
63
64             this._node = null;
65         }
66
67         if (this._canvas)
68             this._canvas.removeEventListener(WebInspector.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
69
70         this._canvas = canvas || null;
71
72         if (this._canvas)
73             this._canvas.addEventListener(WebInspector.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
74
75         this.needsLayout();
76     }
77
78     // Protected
79
80     initialLayout()
81     {
82         super.initialLayout();
83
84         this._nameRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Name"));
85         this._typeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Type"));
86         this._memoryRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Memory"));
87
88         let identitySection = new WebInspector.DetailsSection("canvas-details", WebInspector.UIString("Identity"));
89         identitySection.groups = [new WebInspector.DetailsSectionGroup([this._nameRow, this._typeRow, this._memoryRow])];
90         this.contentView.element.appendChild(identitySection.element);
91
92         this._nodeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Node"));
93         this._cssCanvasRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("CSS Canvas"));
94         this._widthRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Width"));
95         this._heightRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Height"));
96         this._datachedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Detached"));
97
98         let sourceSection = new WebInspector.DetailsSection("canvas-source", WebInspector.UIString("Source"));
99         sourceSection.groups = [new WebInspector.DetailsSectionGroup([this._nodeRow, this._cssCanvasRow, this._widthRow, this._heightRow, this._datachedRow])];
100         this.contentView.element.appendChild(sourceSection.element);
101
102         this._attributesDataGridRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Attributes"));
103
104         let attributesSection = new WebInspector.DetailsSection("canvas-attributes", WebInspector.UIString("Attributes"));
105         attributesSection.groups = [new WebInspector.DetailsSectionGroup([this._attributesDataGridRow])];
106         this.contentView.element.appendChild(attributesSection.element);
107     }
108
109     layout()
110     {
111         super.layout();
112
113         if (!this._canvas)
114             return;
115
116         this._refreshIdentitySection();
117         this._refreshSourceSection();
118         this._refreshAttributesSection();
119     }
120
121     sizeDidChange()
122     {
123         super.sizeDidChange();
124
125         // FIXME: <https://webkit.org/b/152269> Web Inspector: Convert DetailsSection classes to use View
126         this._attributesDataGridRow.sizeDidChange();
127     }
128
129     // Private
130
131     _refreshIdentitySection()
132     {
133         if (!this._canvas)
134             return;
135
136         this._nameRow.value = this._canvas.displayName;
137         this._typeRow.value = WebInspector.Canvas.displayNameForContextType(this._canvas.contextType);
138         this._formatMemoryRow();
139     }
140
141     _refreshSourceSection()
142     {
143         if (!this._canvas)
144             return;
145
146         this._nodeRow.value = this._canvas.cssCanvasName ? null : emDash;
147         this._cssCanvasRow.value = this._canvas.cssCanvasName || null;
148         this._widthRow.value = emDash;
149         this._heightRow.value = emDash;
150         this._datachedRow.value = null;
151
152         this._canvas.requestNode((node) => {
153             if (!node)
154                 return;
155
156             if (node !== this._node) {
157                 if (this._node) {
158                     this._node.removeEventListener(WebInspector.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
159                     this._node.removeEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
160
161                     this._node = null;
162                 }
163
164                 this._node = node;
165
166                 this._node.addEventListener(WebInspector.DOMNode.Event.AttributeModified, this._refreshSourceSection, this);
167                 this._node.addEventListener(WebInspector.DOMNode.Event.AttributeRemoved, this._refreshSourceSection, this);
168             }
169
170             if (!this._canvas.cssCanvasName)
171                 this._nodeRow.value = WebInspector.linkifyNodeReference(this._node);
172
173             let setRowValueIfValidAttributeValue = (row, attribute) => {
174                 let value = Number(this._node.getAttribute(attribute));
175                 if (!Number.isInteger(value) || value < 0)
176                     return false;
177
178                 row.value = value;
179                 return true;
180             };
181
182             let validWidth = setRowValueIfValidAttributeValue(this._widthRow, "width");
183             let validHeight = setRowValueIfValidAttributeValue(this._heightRow, "height");
184             if (!validWidth || !validHeight) {
185                 // Since the "width" and "height" properties of canvas elements are more than just
186                 // attributes, we need to invoke the getter for each to get the actual value.
187                 //  - https://html.spec.whatwg.org/multipage/canvas.html#attr-canvas-width
188                 //  - https://html.spec.whatwg.org/multipage/canvas.html#attr-canvas-height
189                 WebInspector.RemoteObject.resolveNode(node, "", (remoteObject) => {
190                     if (!remoteObject)
191                         return;
192
193                     function setRowValueToPropertyValue(row, property) {
194                         remoteObject.getProperty(property, (error, result, wasThrown) => {
195                             if (!error && result.type === "number")
196                                 row.value = `${result.value}px`;
197                         });
198                     }
199
200                     setRowValueToPropertyValue(this._widthRow, "width");
201                     setRowValueToPropertyValue(this._heightRow, "height");
202
203                     remoteObject.release();
204                 });
205             }
206
207             if (!this._canvas.cssCanvasName && !this._node.parentNode)
208                 this._datachedRow.value = WebInspector.UIString("Yes");
209         });
210     }
211
212     _refreshAttributesSection()
213     {
214         if (!this._canvas)
215             return;
216
217         if (isEmptyObject(this._canvas.contextAttributes)) {
218             // Remove the DataGrid to show the placeholder text.
219             this._attributesDataGridRow.dataGrid = null;
220             return;
221         }
222
223         let dataGrid = this._attributesDataGridRow.dataGrid;
224         if (!dataGrid) {
225             dataGrid = this._attributesDataGridRow.dataGrid = new WebInspector.DataGrid({
226                 name: {title: WebInspector.UIString("Name")},
227                 value: {title: WebInspector.UIString("Value"), width: "30%"},
228             });
229         }
230
231         dataGrid.removeChildren();
232
233         for (let attribute in this._canvas.contextAttributes) {
234             let data = {name: attribute, value: this._canvas.contextAttributes[attribute]};
235             let dataGridNode = new WebInspector.DataGridNode(data);
236             dataGrid.appendChild(dataGridNode);
237         }
238
239         dataGrid.updateLayoutIfNeeded();
240     }
241
242     _formatMemoryRow()
243     {
244         if (!this._canvas.memoryCost || isNaN(this._canvas.memoryCost)) {
245             this._memoryRow.value = emDash;
246             return;
247         }
248
249         let canvasMemory = Number.bytesToString(this._canvas.memoryCost);
250         this._memoryRow.value = canvasMemory;
251         this._memoryRow.tooltip = WebInspector.UIString("Memory usage of this canvas");
252     }
253
254     _canvasMemoryChanged(event)
255     {
256         this._formatMemoryRow();
257     }
258 };