664a8eb9ed7265322b91ea542b053739a76ef270
[WebKit-https.git] / Source / WebCore / inspector / InspectorCanvasAgent.cpp
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 #include "config.h"
27 #include "InspectorCanvasAgent.h"
28
29 #include "CanvasRenderingContext.h"
30 #include "CanvasRenderingContext2D.h"
31 #include "Document.h"
32 #include "Element.h"
33 #include "Frame.h"
34 #include "InspectorDOMAgent.h"
35 #include "InstrumentingAgents.h"
36 #include "JSCanvasRenderingContext2D.h"
37 #include "JSMainThreadExecState.h"
38 #include "MainFrame.h"
39 #include "ScriptState.h"
40 #include "StringAdaptors.h"
41 #include <inspector/IdentifiersFactory.h>
42 #include <inspector/InjectedScript.h>
43 #include <inspector/InjectedScriptManager.h>
44 #include <inspector/InspectorProtocolObjects.h>
45 #include <runtime/JSCInlines.h>
46
47 #if ENABLE(WEBGL)
48 #include "JSWebGLRenderingContext.h"
49 #endif
50
51 #if ENABLE(WEBGL2)
52 #include "JSWebGL2RenderingContext.h"
53 #endif
54
55 #if ENABLE(WEBGPU)
56 #include "JSWebGPURenderingContext.h"
57 #endif
58
59 using namespace Inspector;
60
61 namespace WebCore {
62
63 InspectorCanvasAgent::InspectorCanvasAgent(WebAgentContext& context)
64     : InspectorAgentBase(ASCIILiteral("Canvas"), context)
65     , m_frontendDispatcher(std::make_unique<Inspector::CanvasFrontendDispatcher>(context.frontendRouter))
66     , m_backendDispatcher(Inspector::CanvasBackendDispatcher::create(context.backendDispatcher, this))
67     , m_injectedScriptManager(context.injectedScriptManager)
68     , m_canvasDestroyedTimer(*this, &InspectorCanvasAgent::canvasDestroyedTimerFired)
69     , m_canvasRecordingTimer(*this, &InspectorCanvasAgent::canvasRecordingTimerFired)
70 {
71 }
72
73 void InspectorCanvasAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
74 {
75 }
76
77 void InspectorCanvasAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
78 {
79     ErrorString ignored;
80     disable(ignored);
81 }
82
83 void InspectorCanvasAgent::discardAgent()
84 {
85     clearCanvasData();
86 }
87
88 void InspectorCanvasAgent::enable(ErrorString&)
89 {
90     if (m_enabled)
91         return;
92
93     m_enabled = true;
94
95     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
96         m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents));
97 }
98
99 void InspectorCanvasAgent::disable(ErrorString&)
100 {
101     if (!m_enabled)
102         return;
103
104     if (m_canvasDestroyedTimer.isActive())
105         m_canvasDestroyedTimer.stop();
106
107     m_removedCanvasIdentifiers.clear();
108
109     if (m_canvasRecordingTimer.isActive())
110         m_canvasRecordingTimer.stop();
111
112     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
113         inspectorCanvas->resetRecordingData();
114
115     m_enabled = false;
116 }
117
118 void InspectorCanvasAgent::requestNode(ErrorString& errorString, const String& canvasId, int* nodeId)
119 {
120     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
121     if (!inspectorCanvas)
122         return;
123
124     int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&inspectorCanvas->canvas().document());
125     if (!documentNodeId) {
126         errorString = ASCIILiteral("Document has not been requested");
127         return;
128     }
129
130     *nodeId = m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, &inspectorCanvas->canvas());
131 }
132
133 void InspectorCanvasAgent::requestContent(ErrorString& errorString, const String& canvasId, String* content)
134 {
135     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
136     if (!inspectorCanvas)
137         return;
138
139     CanvasRenderingContext* context = inspectorCanvas->canvas().renderingContext();
140     if (is<CanvasRenderingContext2D>(context)) {
141         auto result = inspectorCanvas->canvas().toDataURL(ASCIILiteral("image/png"));
142         if (result.hasException()) {
143             errorString = result.releaseException().releaseMessage();
144             return;
145         }
146         *content = result.releaseReturnValue().string;
147     }
148 #if ENABLE(WEBGL)
149     else if (is<WebGLRenderingContextBase>(context)) {
150         WebGLRenderingContextBase* gl = downcast<WebGLRenderingContextBase>(context);
151
152         gl->setPreventBufferClearForInspector(true);
153         auto result = inspectorCanvas->canvas().toDataURL(ASCIILiteral("image/png"));
154         gl->setPreventBufferClearForInspector(false);
155
156         if (result.hasException()) {
157             errorString = result.releaseException().releaseMessage();
158             return;
159         }
160         *content = result.releaseReturnValue().string;
161     }
162 #endif
163     // FIXME: <https://webkit.org/b/173621> Web Inspector: Support getting the content of WebGPU contexts
164     else
165         errorString = ASCIILiteral("Unsupported canvas context type");
166 }
167
168 void InspectorCanvasAgent::requestCSSCanvasClientNodes(ErrorString& errorString, const String& canvasId, RefPtr<Inspector::Protocol::Array<int>>& result)
169 {
170     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
171     if (!inspectorCanvas)
172         return;
173
174     result = Inspector::Protocol::Array<int>::create();
175     for (Element* element : inspectorCanvas->canvas().cssCanvasClients()) {
176         if (int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&element->document()))
177             result->addItem(m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, element));
178     }
179 }
180
181 static JSC::JSValue contextAsScriptValue(JSC::ExecState& state, CanvasRenderingContext* context)
182 {
183     JSC::JSLockHolder lock(&state);
184
185     if (is<CanvasRenderingContext2D>(context))
186         return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<CanvasRenderingContext2D>(context));
187 #if ENABLE(WEBGL)
188     if (is<WebGLRenderingContext>(context))
189         return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGLRenderingContext>(context));
190 #endif
191 #if ENABLE(WEBGL2)
192     if (is<WebGL2RenderingContext>(context))
193         return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGL2RenderingContext>(context));
194 #endif
195 #if ENABLE(WEBGPU)
196     if (is<WebGPURenderingContext>(context))
197         return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGPURenderingContext>(context));
198 #endif
199
200     return { };
201 }
202
203 void InspectorCanvasAgent::resolveCanvasContext(ErrorString& errorString, const String& canvasId, const String* const objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result)
204 {
205     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
206     if (!inspectorCanvas)
207         return;
208
209     Frame* frame = inspectorCanvas->canvas().document().frame();
210     if (!frame) {
211         errorString = ASCIILiteral("Canvas belongs to a document without a frame");
212         return;
213     }
214
215     auto& state = *mainWorldExecState(frame);
216     auto injectedScript = m_injectedScriptManager.injectedScriptFor(&state);
217     ASSERT(!injectedScript.hasNoValue());
218
219     CanvasRenderingContext* context = inspectorCanvas->canvas().renderingContext();
220     JSC::JSValue value = contextAsScriptValue(state, context);
221     if (!value) {
222         ASSERT_NOT_REACHED();
223         errorString = ASCIILiteral("Unknown context type");
224         return;
225     }
226
227     String objectGroupName = objectGroup ? *objectGroup : String();
228     result = injectedScript.wrapObject(value, objectGroupName);
229 }
230
231 void InspectorCanvasAgent::requestRecording(ErrorString& errorString, const String& canvasId, const bool* const singleFrame, const int* const memoryLimit)
232 {
233     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
234     if (!inspectorCanvas)
235         return;
236
237     if (inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
238         errorString = ASCIILiteral("Already recording canvas");
239         return;
240     }
241
242     inspectorCanvas->resetRecordingData();
243     if (singleFrame)
244         inspectorCanvas->setSingleFrame(*singleFrame);
245     if (memoryLimit)
246         inspectorCanvas->setBufferLimit(*memoryLimit);
247
248     inspectorCanvas->canvas().renderingContext()->setCallTracingActive(true);
249 }
250
251 void InspectorCanvasAgent::cancelRecording(ErrorString& errorString, const String& canvasId)
252 {
253     auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
254     if (!inspectorCanvas)
255         return;
256
257     if (!inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
258         errorString = ASCIILiteral("No active recording for canvas");
259         return;
260     }
261
262     didFinishRecordingCanvasFrame(inspectorCanvas->canvas(), true);
263 }
264
265 void InspectorCanvasAgent::frameNavigated(Frame& frame)
266 {
267     if (frame.isMainFrame()) {
268         clearCanvasData();
269         return;
270     }
271
272     Vector<InspectorCanvas*> inspectorCanvases;
273     for (RefPtr<InspectorCanvas>& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
274         if (inspectorCanvas->canvas().document().frame() == &frame)
275             inspectorCanvases.append(inspectorCanvas.get());
276     }
277
278     for (auto* inspectorCanvas : inspectorCanvases) {
279         String identifier = unbindCanvas(*inspectorCanvas);
280         if (m_enabled)
281             m_frontendDispatcher->canvasRemoved(identifier);
282     }
283 }
284
285 void InspectorCanvasAgent::didCreateCSSCanvas(HTMLCanvasElement& canvasElement, const String& name)
286 {
287     if (findInspectorCanvas(canvasElement)) {
288         ASSERT_NOT_REACHED();
289         return;
290     }
291
292     ASSERT(!m_canvasToCSSCanvasName.contains(&canvasElement));
293     m_canvasToCSSCanvasName.set(&canvasElement, name);
294 }
295
296 void InspectorCanvasAgent::didChangeCSSCanvasClientNodes(HTMLCanvasElement& canvasElement)
297 {
298     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
299     if (!inspectorCanvas)
300         return;
301
302     m_frontendDispatcher->cssCanvasClientNodesChanged(inspectorCanvas->identifier());
303 }
304
305 void InspectorCanvasAgent::didCreateCanvasRenderingContext(HTMLCanvasElement& canvasElement)
306 {
307     if (findInspectorCanvas(canvasElement)) {
308         ASSERT_NOT_REACHED();
309         return;
310     }
311
312     canvasElement.addObserver(*this);
313
314     String cssCanvasName = m_canvasToCSSCanvasName.take(&canvasElement);
315     auto inspectorCanvas = InspectorCanvas::create(canvasElement, cssCanvasName);
316
317     if (m_enabled)
318         m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents));
319
320     m_identifierToInspectorCanvas.set(inspectorCanvas->identifier(), WTFMove(inspectorCanvas));
321 }
322
323 void InspectorCanvasAgent::didChangeCanvasMemory(HTMLCanvasElement& canvasElement)
324 {
325     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
326     if (!inspectorCanvas)
327         return;
328
329     m_frontendDispatcher->canvasMemoryChanged(inspectorCanvas->identifier(), canvasElement.memoryCost());
330 }
331
332 void InspectorCanvasAgent::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<RecordCanvasActionVariant>&& parameters)
333 {
334     HTMLCanvasElement& canvasElement = canvasRenderingContext.canvas();
335
336     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
337     ASSERT(inspectorCanvas);
338     if (!inspectorCanvas)
339         return;
340
341     ASSERT(canvasRenderingContext.callTracingActive());
342     if (!canvasRenderingContext.callTracingActive())
343         return;
344
345     inspectorCanvas->recordAction(name, WTFMove(parameters));
346
347     if (!m_canvasRecordingTimer.isActive())
348         m_canvasRecordingTimer.startOneShot(0_s);
349
350     if (!inspectorCanvas->hasBufferSpace())
351         didFinishRecordingCanvasFrame(canvasElement, true);
352 }
353
354 void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
355 {
356     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
357     ASSERT(inspectorCanvas);
358     if (!inspectorCanvas)
359         return;
360
361     String identifier = unbindCanvas(*inspectorCanvas);
362     if (!m_enabled)
363         return;
364
365     // WebCore::CanvasObserver::canvasDestroyed is called in response to the GC destroying the HTMLCanvasElement.
366     // Due to the single-process model used in WebKit1, the event must be dispatched from a timer to prevent
367     // the frontend from making JS allocations while the GC is still active.
368     m_removedCanvasIdentifiers.append(identifier);
369
370     if (!m_canvasDestroyedTimer.isActive())
371         m_canvasDestroyedTimer.startOneShot(0_s);
372 }
373
374 void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canvasElement, bool forceDispatch)
375 {
376     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
377     ASSERT(inspectorCanvas);
378     if (!inspectorCanvas)
379         return;
380
381     CanvasRenderingContext* canvasRenderingContext = inspectorCanvas->canvas().renderingContext();
382     ASSERT(canvasRenderingContext->callTracingActive());
383     if (!canvasRenderingContext->callTracingActive())
384         return;
385
386     if (!inspectorCanvas->hasRecordingData())
387         return;
388
389     if (!forceDispatch && !inspectorCanvas->singleFrame()) {
390         inspectorCanvas->markNewFrame();
391         return;
392     }
393
394     if (forceDispatch)
395         inspectorCanvas->markCurrentFrameIncomplete();
396
397     // <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext
398
399     Inspector::Protocol::Recording::Type type;
400     if (is<CanvasRenderingContext2D>(canvasRenderingContext))
401         type = Inspector::Protocol::Recording::Type::Canvas2D;
402     else {
403         ASSERT_NOT_REACHED();
404         type = Inspector::Protocol::Recording::Type::Canvas2D;
405     }
406
407     auto recording = Inspector::Protocol::Recording::Recording::create()
408         .setVersion(1)
409         .setType(type)
410         .setInitialState(inspectorCanvas->releaseInitialState())
411         .setFrames(inspectorCanvas->releaseFrames())
412         .setData(inspectorCanvas->releaseData())
413         .release();
414
415     m_frontendDispatcher->recordingFinished(inspectorCanvas->identifier(), WTFMove(recording));
416
417     inspectorCanvas->resetRecordingData();
418 }
419
420 void InspectorCanvasAgent::canvasDestroyedTimerFired()
421 {
422     if (!m_removedCanvasIdentifiers.size())
423         return;
424
425     if (m_enabled) {
426         for (auto& identifier : m_removedCanvasIdentifiers)
427             m_frontendDispatcher->canvasRemoved(identifier);
428     }
429
430     m_removedCanvasIdentifiers.clear();
431 }
432
433 void InspectorCanvasAgent::canvasRecordingTimerFired()
434 {
435     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
436         if (!inspectorCanvas->canvas().renderingContext()->callTracingActive())
437             continue;
438
439         didFinishRecordingCanvasFrame(inspectorCanvas->canvas());
440     }
441 }
442
443 void InspectorCanvasAgent::clearCanvasData()
444 {
445     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
446         inspectorCanvas->canvas().removeObserver(*this);
447
448     m_identifierToInspectorCanvas.clear();
449     m_canvasToCSSCanvasName.clear();
450     m_removedCanvasIdentifiers.clear();
451
452     if (m_canvasRecordingTimer.isActive())
453         m_canvasRecordingTimer.stop();
454
455     if (m_canvasDestroyedTimer.isActive())
456         m_canvasDestroyedTimer.stop();
457 }
458
459 String InspectorCanvasAgent::unbindCanvas(InspectorCanvas& inspectorCanvas)
460 {
461     ASSERT(!m_canvasToCSSCanvasName.contains(&inspectorCanvas.canvas()));
462
463     String identifier = inspectorCanvas.identifier();
464     m_identifierToInspectorCanvas.remove(identifier);
465
466     return identifier;
467 }
468
469 InspectorCanvas* InspectorCanvasAgent::assertInspectorCanvas(ErrorString& errorString, const String& identifier)
470 {
471     RefPtr<InspectorCanvas> inspectorCanvas = m_identifierToInspectorCanvas.get(identifier);
472     if (!inspectorCanvas) {
473         errorString = ASCIILiteral("No canvas for given identifier.");
474         return nullptr;
475     }
476
477     return inspectorCanvas.get();
478 }
479
480 InspectorCanvas* InspectorCanvasAgent::findInspectorCanvas(HTMLCanvasElement& canvasElement)
481 {
482     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
483         if (&inspectorCanvas->canvas() == &canvasElement)
484             return inspectorCanvas.get();
485     }
486
487     return nullptr;
488 }
489
490 } // namespace WebCore