Web Inspector: Canvas: auto-record after page load sometimes shows the wrong UI
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / CanvasOverviewContentView.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 WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.CollectionContentView
27 {
28     constructor(representedObject)
29     {
30         console.assert(representedObject instanceof WI.CanvasCollection);
31
32         let contentPlaceholder = WI.createMessageTextView(WI.UIString("No Canvas Contexts"));
33         let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
34         descriptionElement.className = "description";
35         descriptionElement.textContent = WI.UIString("Waiting for canvas contexts created by script or CSS.");
36
37         let importNavigationItem = new WI.ButtonNavigationItem("import-recording", WI.UIString("Import"), "Images/Import.svg", 15, 15);
38         importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
39
40         let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to load a recording from file."), importNavigationItem);
41         contentPlaceholder.appendChild(importHelpElement);
42
43         super(representedObject, WI.CanvasContentView, contentPlaceholder);
44
45         this.element.classList.add("canvas-overview");
46
47         if (WI.CanvasManager.supportsRecordingAutoCapture()) {
48             this._recordingAutoCaptureFrameCountInputElement = document.createElement("input");
49             this._recordingAutoCaptureFrameCountInputElement.type = "number";
50             this._recordingAutoCaptureFrameCountInputElement.min = 0;
51             this._recordingAutoCaptureFrameCountInputElement.style.setProperty("--recording-auto-capture-input-margin", CanvasOverviewContentView.recordingAutoCaptureInputMargin + "px");
52             this._recordingAutoCaptureFrameCountInputElement.addEventListener("input", this._handleRecordingAutoCaptureInput.bind(this));
53             this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
54
55             const label = null;
56             this._recordingAutoCaptureNavigationItem = new WI.CheckboxNavigationItem("canvas-recording-auto-capture", label, !!WI.settings.canvasRecordingAutoCaptureEnabled.value);
57             this._recordingAutoCaptureNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
58             this._recordingAutoCaptureNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, this._handleRecordingAutoCaptureCheckedDidChange, this);
59
60             let frameCount = this._updateRecordingAutoCaptureInputElementSize();
61             this._setRecordingAutoCaptureFrameCount(frameCount);
62             this._updateRecordingAutoCaptureCheckboxLabel(frameCount);
63         }
64
65         this._importButtonNavigationItem = new WI.ButtonNavigationItem("import-recording", WI.UIString("Import"), "Images/Import.svg", 15, 15);
66         this._importButtonNavigationItem.tooltip = WI.UIString("Import");
67         this._importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
68
69         this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("refresh-all", WI.UIString("Refresh all"), "Images/ReloadFull.svg", 13, 13);
70         this._refreshButtonNavigationItem.enabled = false;
71         this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshPreviews, this);
72
73         this._showGridButtonNavigationItem = new WI.ActivateButtonNavigationItem("show-grid", WI.UIString("Show transparency grid"), WI.UIString("Hide Grid"), "Images/NavigationItemCheckers.svg", 13, 13);
74         this._showGridButtonNavigationItem.activated = !!WI.settings.showImageGrid.value;
75         this._showGridButtonNavigationItem.enabled = false;
76         this._showGridButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._showGridButtonClicked, this);
77
78         importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
79         this._importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
80     }
81
82     // Static
83
84     static get recordingAutoCaptureInputMargin() { return 4; }
85
86     // Public
87
88     get navigationItems()
89     {
90         let navigationItems = [this._importButtonNavigationItem, new WI.DividerNavigationItem, this._refreshButtonNavigationItem, this._showGridButtonNavigationItem];
91         if (WI.CanvasManager.supportsRecordingAutoCapture())
92             navigationItems.unshift(this._recordingAutoCaptureNavigationItem, new WI.DividerNavigationItem);
93         return navigationItems;
94     }
95
96     hidden()
97     {
98         WI.domManager.hideDOMNodeHighlight();
99
100         super.hidden();
101     }
102
103     // Protected
104
105     contentViewAdded(contentView)
106     {
107         contentView.element.addEventListener("mouseenter", this._contentViewMouseEnter);
108         contentView.element.addEventListener("mouseleave", this._contentViewMouseLeave);
109
110         this._updateNavigationItems();
111     }
112
113     contentViewRemoved(contentView)
114     {
115         contentView.element.removeEventListener("mouseenter", this._contentViewMouseEnter);
116         contentView.element.removeEventListener("mouseleave", this._contentViewMouseLeave);
117
118         this._updateNavigationItems();
119     }
120
121     attached()
122     {
123         super.attached();
124
125         WI.settings.showImageGrid.addEventListener(WI.Setting.Event.Changed, this._updateShowImageGrid, this);
126         WI.settings.canvasRecordingAutoCaptureEnabled.addEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureEnabledChanged, this);
127         WI.settings.canvasRecordingAutoCaptureFrameCount.addEventListener(WI.Setting.Event.Changed, this._handleCanvasRecordingAutoCaptureFrameCountChanged, this);
128     }
129
130     detached()
131     {
132         WI.settings.canvasRecordingAutoCaptureFrameCount.removeEventListener(null, null, this);
133         WI.settings.canvasRecordingAutoCaptureEnabled.removeEventListener(null, null, this);
134         WI.settings.showImageGrid.removeEventListener(null, null, this);
135
136         super.detached();
137     }
138
139     // Private
140
141     get _itemMargin()
142     {
143         return parseInt(window.getComputedStyle(this.element).getPropertyValue("--item-margin"));
144     }
145
146     _refreshPreviews()
147     {
148         for (let canvasContentView of this.subviews)
149             canvasContentView.refreshPreview();
150     }
151
152     _updateNavigationItems()
153     {
154         let hasItems = !!this.representedObject.size;
155         this._refreshButtonNavigationItem.enabled = hasItems;
156         this._showGridButtonNavigationItem.enabled = hasItems;
157     }
158
159     _showGridButtonClicked(event)
160     {
161         WI.settings.showImageGrid.value = !this._showGridButtonNavigationItem.activated;
162     }
163
164     _updateShowImageGrid()
165     {
166         this._showGridButtonNavigationItem.activated = !!WI.settings.showImageGrid.value;
167     }
168
169     _contentViewMouseEnter(event)
170     {
171         let contentView = WI.View.fromElement(event.target);
172         if (!(contentView instanceof WI.CanvasContentView))
173             return;
174
175         let canvas = contentView.representedObject;
176         if (canvas.cssCanvasName) {
177             canvas.requestCSSCanvasClientNodes((cssCanvasClientNodes) => {
178                 WI.domManager.highlightDOMNodeList(cssCanvasClientNodes.map((node) => node.id));
179             });
180             return;
181         }
182
183         canvas.requestNode().then((node) => {
184             if (!node || !node.ownerDocument)
185                 return;
186             WI.domManager.highlightDOMNode(node.id);
187         });
188     }
189
190     _contentViewMouseLeave(event)
191     {
192         WI.domManager.hideDOMNodeHighlight();
193     }
194
195     _setRecordingAutoCaptureFrameCount(frameCount)
196     {
197         console.assert(!isNaN(frameCount) && frameCount >= 0);
198
199         if (this._recordingAutoCaptureNavigationItem.checked)
200             frameCount = Math.max(1, frameCount);
201
202         let enabled = frameCount > 0 && !!this._recordingAutoCaptureNavigationItem.checked;
203
204         WI.canvasManager.setRecordingAutoCaptureFrameCount(enabled, frameCount);
205     }
206
207     _updateRecordingAutoCaptureCheckboxLabel(frameCount)
208     {
209         let active = document.activeElement === this._recordingAutoCaptureFrameCountInputElement;
210         let selectionStart = this._recordingAutoCaptureFrameCountInputElement.selectionStart;
211         let selectionEnd = this._recordingAutoCaptureFrameCountInputElement.selectionEnd;
212         let direction = this._recordingAutoCaptureFrameCountInputElement.direction;
213
214         let label = frameCount === 1 ? WI.UIString("Record first %s frame") : WI.UIString("Record first %s frames");
215         let fragment = document.createDocumentFragment();
216         String.format(label, [this._recordingAutoCaptureFrameCountInputElement], String.standardFormatters, fragment, (a, b) => {
217             a.append(b);
218             return a;
219         });
220         this._recordingAutoCaptureNavigationItem.label = fragment;
221
222         if (active) {
223             this._recordingAutoCaptureFrameCountInputElement.selectionStart = selectionStart;
224             this._recordingAutoCaptureFrameCountInputElement.selectionEnd = selectionEnd;
225             this._recordingAutoCaptureFrameCountInputElement.direction = direction;
226         }
227     }
228
229     get _recordingAutoCaptureFrameCountInputElementValue()
230     {
231         return parseInt(this._recordingAutoCaptureFrameCountInputElement.value);
232     }
233
234     set _recordingAutoCaptureFrameCountInputElementValue(frameCount)
235     {
236         if (this._recordingAutoCaptureFrameCountInputElement.value || frameCount)
237             this._recordingAutoCaptureFrameCountInputElement.value = frameCount;
238
239         this._recordingAutoCaptureFrameCountInputElement.placeholder = frameCount;
240     }
241
242     _updateRecordingAutoCaptureInputElementSize()
243     {
244         let frameCount = this._recordingAutoCaptureFrameCountInputElementValue;
245         if (isNaN(frameCount) || frameCount < 0) {
246             frameCount = 0;
247             this._recordingAutoCaptureFrameCountInputElementValue = frameCount;
248         }
249
250         WI.ImageUtilities.scratchCanvasContext2D((context) => {
251             if (!this._recordingAutoCaptureFrameCountInputElement.__cachedFont) {
252                 let computedStyle = window.getComputedStyle(this._recordingAutoCaptureFrameCountInputElement);
253                 this._recordingAutoCaptureFrameCountInputElement.__cachedFont = computedStyle.font;
254             }
255
256             context.font = this._recordingAutoCaptureFrameCountInputElement.__cachedFont;
257             let textMetrics = context.measureText(this._recordingAutoCaptureFrameCountInputElement.value || this._recordingAutoCaptureFrameCountInputElement.placeholder);
258             this._recordingAutoCaptureFrameCountInputElement.style.setProperty("width", (textMetrics.width + (2 * CanvasOverviewContentView.recordingAutoCaptureInputMargin)) + "px");
259         });
260
261         return frameCount;
262     }
263
264     _handleRecordingAutoCaptureInput(event)
265     {
266         let frameCount = this._updateRecordingAutoCaptureInputElementSize();
267         this._recordingAutoCaptureNavigationItem.checked = !!frameCount;
268
269         this._setRecordingAutoCaptureFrameCount(frameCount);
270     }
271
272     _handleRecordingAutoCaptureCheckedDidChange(event)
273     {
274         this._setRecordingAutoCaptureFrameCount(this._recordingAutoCaptureFrameCountInputElementValue || 0);
275     }
276
277     _handleCanvasRecordingAutoCaptureEnabledChanged(event)
278     {
279         this._recordingAutoCaptureNavigationItem.checked = WI.settings.canvasRecordingAutoCaptureEnabled.value;
280     }
281
282     _handleCanvasRecordingAutoCaptureFrameCountChanged(event)
283     {
284         // Only update the value if it is different to prevent mangling the selection.
285         if (this._recordingAutoCaptureFrameCountInputElementValue !== WI.settings.canvasRecordingAutoCaptureFrameCount.value)
286             this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
287
288         this._updateRecordingAutoCaptureCheckboxLabel(WI.settings.canvasRecordingAutoCaptureFrameCount.value);
289     }
290
291     _handleImportButtonNavigationItemClicked(event)
292     {
293         WI.FileUtilities.importJSON((result) => WI.canvasManager.processJSON(result));
294     }
295 };