18522485a6522b55d32830b16f3f8cc620d37f3c
[WebKit-https.git] / Source / WebCore / inspector / agents / InspectorCanvasAgent.cpp
1 /*
2  * Copyright (C) 2017-2019 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 "ActiveDOMCallbackMicrotask.h"
30 #include "CanvasRenderingContext.h"
31 #include "CanvasRenderingContext2D.h"
32 #include "Document.h"
33 #include "Element.h"
34 #include "Frame.h"
35 #include "HTMLCanvasElement.h"
36 #include "ImageBitmapRenderingContext.h"
37 #include "InspectorDOMAgent.h"
38 #include "InstrumentingAgents.h"
39 #include "JSExecState.h"
40 #include "Microtasks.h"
41 #include "OffscreenCanvas.h"
42 #include "ScriptState.h"
43 #include "StringAdaptors.h"
44 #include <JavaScriptCore/IdentifiersFactory.h>
45 #include <JavaScriptCore/InjectedScript.h>
46 #include <JavaScriptCore/InjectedScriptManager.h>
47 #include <JavaScriptCore/InspectorProtocolObjects.h>
48 #include <JavaScriptCore/JSCInlines.h>
49 #include <wtf/HashSet.h>
50 #include <wtf/Lock.h>
51
52 #if ENABLE(WEBGL)
53 #include "WebGLProgram.h"
54 #include "WebGLRenderingContext.h"
55 #include "WebGLRenderingContextBase.h"
56 #include "WebGLShader.h"
57 #endif
58
59 #if ENABLE(WEBGL2)
60 #include "WebGL2RenderingContext.h"
61 #endif
62
63 #if ENABLE(WEBGPU)
64 #include "GPUCanvasContext.h"
65 #include "WebGPUDevice.h"
66 #endif
67
68 namespace WebCore {
69
70 using namespace Inspector;
71
72 InspectorCanvasAgent::InspectorCanvasAgent(PageAgentContext& context)
73     : InspectorAgentBase("Canvas"_s, context)
74     , m_frontendDispatcher(makeUnique<Inspector::CanvasFrontendDispatcher>(context.frontendRouter))
75     , m_backendDispatcher(Inspector::CanvasBackendDispatcher::create(context.backendDispatcher, this))
76     , m_injectedScriptManager(context.injectedScriptManager)
77     , m_inspectedPage(context.inspectedPage)
78     , m_canvasDestroyedTimer(*this, &InspectorCanvasAgent::canvasDestroyedTimerFired)
79 {
80 }
81
82 InspectorCanvasAgent::~InspectorCanvasAgent() = default;
83
84 void InspectorCanvasAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
85 {
86 }
87
88 void InspectorCanvasAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
89 {
90     ErrorString ignored;
91     disable(ignored);
92 }
93
94 void InspectorCanvasAgent::discardAgent()
95 {
96     clearCanvasData();
97 }
98
99 void InspectorCanvasAgent::enable(ErrorString&)
100 {
101     if (m_instrumentingAgents.inspectorCanvasAgent() == this)
102         return;
103
104     m_instrumentingAgents.setInspectorCanvasAgent(this);
105
106     const auto existsInCurrentPage = [&] (ScriptExecutionContext* scriptExecutionContext) {
107         if (!is<Document>(scriptExecutionContext))
108             return false;
109
110         // FIXME: <https://webkit.org/b/168475> Web Inspector: Correctly display iframe's WebSockets
111         auto* document = downcast<Document>(scriptExecutionContext);
112         return document->page() == &m_inspectedPage;
113     };
114
115     {
116         LockHolder lock(CanvasRenderingContext::instancesMutex());
117         for (auto* context : CanvasRenderingContext::instances(lock)) {
118 #if ENABLE(WEBGPU)
119             // The actual "context" for WebGPU is the `WebGPUDevice`, not the <canvas>.
120             if (is<GPUCanvasContext>(context))
121                 continue;
122 #endif
123
124             if (existsInCurrentPage(context->canvasBase().scriptExecutionContext()))
125                 bindCanvas(*context, false);
126         }
127     }
128
129 #if ENABLE(WEBGPU)
130     {
131         LockHolder lock(WebGPUDevice::instancesMutex());
132         for (auto* device : WebGPUDevice::instances(lock)) {
133             if (existsInCurrentPage(device->scriptExecutionContext()))
134                 bindCanvas(*device, false);
135         }
136     }
137 #endif
138
139 #if ENABLE(WEBGL)
140     {
141         LockHolder lock(WebGLProgram::instancesMutex());
142         for (auto& [program, contextWebGLBase] : WebGLProgram::instances(lock)) {
143             if (contextWebGLBase && existsInCurrentPage(contextWebGLBase->canvasBase().scriptExecutionContext()))
144                 didCreateProgram(*contextWebGLBase, *program);
145         }
146     }
147 #endif
148 }
149
150 void InspectorCanvasAgent::disable(ErrorString&)
151 {
152     m_instrumentingAgents.setInspectorCanvasAgent(nullptr);
153
154     clearCanvasData();
155
156     m_recordingAutoCaptureFrameCount = WTF::nullopt;
157 }
158
159 void InspectorCanvasAgent::requestNode(ErrorString& errorString, const String& canvasId, int* nodeId)
160 {
161     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
162     if (!inspectorCanvas)
163         return;
164
165     auto* node = inspectorCanvas->canvasElement();
166     if (!node) {
167         errorString = "Missing element of canvas for given canvasId"_s;
168         return;
169     }
170
171     int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&node->document());
172     if (!documentNodeId) {
173         errorString = "Document must have been requested"_s;
174         return;
175     }
176
177     *nodeId = m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, node);
178 }
179
180 void InspectorCanvasAgent::requestContent(ErrorString& errorString, const String& canvasId, String* content)
181 {
182     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
183     if (!inspectorCanvas)
184         return;
185
186     *content = inspectorCanvas->getCanvasContentAsDataURL(errorString);
187 }
188
189 void InspectorCanvasAgent::requestClientNodes(ErrorString& errorString, const String& canvasId, RefPtr<JSON::ArrayOf<int>>& clientNodeIds)
190 {
191     auto* domAgent = m_instrumentingAgents.inspectorDOMAgent();
192     if (!domAgent) {
193         errorString = "DOM domain must be enabled"_s;
194         return;
195     }
196
197     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
198     if (!inspectorCanvas)
199         return;
200
201     clientNodeIds = JSON::ArrayOf<int>::create();
202     for (auto& clientNode : inspectorCanvas->clientNodes()) {
203         if (auto documentNodeId = domAgent->boundNodeId(&clientNode->document()))
204             clientNodeIds->addItem(domAgent->pushNodeToFrontend(errorString, documentNodeId, clientNode));
205     }
206 }
207
208 void InspectorCanvasAgent::resolveContext(ErrorString& errorString, const String& canvasId, const String* objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result)
209 {
210     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
211     if (!inspectorCanvas)
212         return;
213
214     auto* state = inspectorCanvas->scriptExecutionContext()->execState();
215     auto injectedScript = m_injectedScriptManager.injectedScriptFor(state);
216     ASSERT(!injectedScript.hasNoValue());
217
218     JSC::JSValue value = inspectorCanvas->resolveContext(state);
219
220     if (!value) {
221         ASSERT_NOT_REACHED();
222         errorString = "Internal error: unknown context of canvas for given canvasId"_s;
223         return;
224     }
225
226     String objectGroupName = objectGroup ? *objectGroup : String();
227     result = injectedScript.wrapObject(value, objectGroupName);
228 }
229
230 void InspectorCanvasAgent::setRecordingAutoCaptureFrameCount(ErrorString&, int count)
231 {
232     if (count > 0)
233         m_recordingAutoCaptureFrameCount = count;
234     else
235         m_recordingAutoCaptureFrameCount = WTF::nullopt;
236 }
237
238 void InspectorCanvasAgent::startRecording(ErrorString& errorString, const String& canvasId, const int* frameCount, const int* memoryLimit)
239 {
240     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
241     if (!inspectorCanvas)
242         return;
243
244     // FIXME: <https://webkit.org/b/201651> Web Inspector: Canvas: support canvas recordings for WebGPUDevice
245
246     auto* context = inspectorCanvas->canvasContext();
247     if (!context)
248         return;
249
250     if (context->callTracingActive()) {
251         errorString = "Already recording canvas"_s;
252         return;
253     }
254
255     RecordingOptions recordingOptions;
256     if (frameCount)
257         recordingOptions.frameCount = *frameCount;
258     if (memoryLimit)
259         recordingOptions.memoryLimit = *memoryLimit;
260     startRecording(*inspectorCanvas, Inspector::Protocol::Recording::Initiator::Frontend, WTFMove(recordingOptions));
261 }
262
263 void InspectorCanvasAgent::stopRecording(ErrorString& errorString, const String& canvasId)
264 {
265     auto inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
266     if (!inspectorCanvas)
267         return;
268
269     // FIXME: <https://webkit.org/b/201651> Web Inspector: Canvas: support canvas recordings for WebGPUDevice
270
271     auto* context = inspectorCanvas->canvasContext();
272     if (!context)
273         return;
274
275     if (!context->callTracingActive()) {
276         errorString = "Not recording canvas"_s;
277         return;
278     }
279
280     didFinishRecordingCanvasFrame(*context, true);
281 }
282
283 void InspectorCanvasAgent::requestShaderSource(ErrorString& errorString, const String& programId, const String& shaderType, String* content)
284 {
285 #if ENABLE(WEBGL)
286     auto inspectorProgram = assertInspectorProgram(errorString, programId);
287     if (!inspectorProgram)
288         return;
289
290     auto* shader = inspectorProgram->shaderForType(shaderType);
291     if (!shader) {
292         errorString = "Missing shader for given shaderType"_s;
293         return;
294     }
295
296     *content = shader->getSource();
297 #else
298     UNUSED_PARAM(programId);
299     UNUSED_PARAM(shaderType);
300     UNUSED_PARAM(content);
301     errorString = "Not supported"_s;
302 #endif
303 }
304
305 void InspectorCanvasAgent::updateShader(ErrorString& errorString, const String& programId, const String& shaderType, const String& source)
306 {
307 #if ENABLE(WEBGL)
308     auto inspectorProgram = assertInspectorProgram(errorString, programId);
309     if (!inspectorProgram)
310         return;
311
312     auto* shader = inspectorProgram->shaderForType(shaderType);
313     if (!shader) {
314         errorString = "Missing shader for given shaderType"_s;
315         return;
316     }
317
318     WebGLRenderingContextBase& contextWebGL = inspectorProgram->context();
319     contextWebGL.shaderSource(shader, source);
320     contextWebGL.compileShader(shader);
321
322     if (!shader->isValid()) {
323         errorString = "Failed to update shader"_s;
324         return;
325     }
326
327     contextWebGL.linkProgramWithoutInvalidatingAttribLocations(&inspectorProgram->program());
328 #else
329     UNUSED_PARAM(programId);
330     UNUSED_PARAM(shaderType);
331     UNUSED_PARAM(source);
332     errorString = "Not supported"_s;
333 #endif
334 }
335
336 void InspectorCanvasAgent::setShaderProgramDisabled(ErrorString& errorString, const String& programId, bool disabled)
337 {
338 #if ENABLE(WEBGL)
339     auto inspectorProgram = assertInspectorProgram(errorString, programId);
340     if (!inspectorProgram)
341         return;
342
343     inspectorProgram->setDisabled(disabled);
344 #else
345     UNUSED_PARAM(programId);
346     UNUSED_PARAM(disabled);
347     errorString = "Not supported"_s;
348 #endif
349 }
350
351 void InspectorCanvasAgent::setShaderProgramHighlighted(ErrorString& errorString, const String& programId, bool highlighted)
352 {
353 #if ENABLE(WEBGL)
354     auto inspectorProgram = assertInspectorProgram(errorString, programId);
355     if (!inspectorProgram)
356         return;
357
358     inspectorProgram->setHighlighted(highlighted);
359 #else
360     UNUSED_PARAM(programId);
361     UNUSED_PARAM(highlighted);
362     errorString = "Not supported"_s;
363 #endif
364 }
365
366 void InspectorCanvasAgent::frameNavigated(Frame& frame)
367 {
368     if (frame.isMainFrame()) {
369         clearCanvasData();
370         return;
371     }
372
373     Vector<InspectorCanvas*> inspectorCanvases;
374     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
375         if (auto* canvasElement = inspectorCanvas->canvasElement()) {
376             if (canvasElement->document().frame() == &frame)
377                 inspectorCanvases.append(inspectorCanvas.get());
378         }
379     }
380
381     for (auto* inspectorCanvas : inspectorCanvases)
382         unbindCanvas(*inspectorCanvas);
383 }
384
385 void InspectorCanvasAgent::didChangeCSSCanvasClientNodes(CanvasBase& canvasBase)
386 {
387     auto* context = canvasBase.renderingContext();
388     if (!context) {
389         ASSERT_NOT_REACHED();
390         return;
391     }
392
393     auto inspectorCanvas = findInspectorCanvas(*context);
394     ASSERT(inspectorCanvas);
395     if (!inspectorCanvas)
396         return;
397
398     m_frontendDispatcher->clientNodesChanged(inspectorCanvas->identifier());
399 }
400
401 void InspectorCanvasAgent::didCreateCanvasRenderingContext(CanvasRenderingContext& context)
402 {
403     if (findInspectorCanvas(context)) {
404         ASSERT_NOT_REACHED();
405         return;
406     }
407
408     auto& inspectorCanvas = bindCanvas(context, true);
409
410     if (m_recordingAutoCaptureFrameCount) {
411         RecordingOptions recordingOptions;
412         recordingOptions.frameCount = m_recordingAutoCaptureFrameCount.value();
413         startRecording(inspectorCanvas, Inspector::Protocol::Recording::Initiator::AutoCapture, WTFMove(recordingOptions));
414     }
415 }
416
417 void InspectorCanvasAgent::didChangeCanvasMemory(CanvasRenderingContext& context)
418 {
419     RefPtr<InspectorCanvas> inspectorCanvas;
420
421 #if ENABLE(WEBGPU)
422     if (is<GPUCanvasContext>(context)) {
423         for (auto& item : m_identifierToInspectorCanvas.values()) {
424             if (item->isDeviceForCanvasContext(context)) {
425                 inspectorCanvas = item;
426                 break;
427             }
428         }
429     }
430 #endif
431
432     if (!inspectorCanvas)
433         inspectorCanvas = findInspectorCanvas(context);
434
435     ASSERT(inspectorCanvas);
436     if (!inspectorCanvas)
437         return;
438
439     // FIXME: <https://webkit.org/b/180833> Web Inspector: support OffscreenCanvas for Canvas related operations
440
441     if (auto* node = inspectorCanvas->canvasElement())
442         m_frontendDispatcher->canvasMemoryChanged(inspectorCanvas->identifier(), node->memoryCost());
443 }
444
445 void InspectorCanvasAgent::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, std::initializer_list<RecordCanvasActionVariant>&& parameters)
446 {
447     auto inspectorCanvas = findInspectorCanvas(canvasRenderingContext);
448     ASSERT(inspectorCanvas);
449     if (!inspectorCanvas)
450         return;
451
452     ASSERT(canvasRenderingContext.callTracingActive());
453     if (!canvasRenderingContext.callTracingActive())
454         return;
455
456     // Only enqueue a microtask for the first action of each frame. Any subsequent actions will be
457     // covered by the initial microtask until the next frame.
458     if (!inspectorCanvas->currentFrameHasData()) {
459         if (auto* scriptExecutionContext = inspectorCanvas->scriptExecutionContext()) {
460             auto& queue = MicrotaskQueue::mainThreadQueue();
461             queue.append(makeUnique<ActiveDOMCallbackMicrotask>(queue, *scriptExecutionContext, [&, protectedInspectorCanvas = inspectorCanvas.copyRef()] {
462                 if (auto* canvasElement = protectedInspectorCanvas->canvasElement()) {
463                     if (canvasElement->isDescendantOf(canvasElement->document()))
464                         return;
465                 }
466
467                 if (canvasRenderingContext.callTracingActive())
468                     didFinishRecordingCanvasFrame(canvasRenderingContext);
469             }));
470         }
471     }
472
473     inspectorCanvas->recordAction(name, WTFMove(parameters));
474
475     if (!inspectorCanvas->hasBufferSpace())
476         didFinishRecordingCanvasFrame(canvasRenderingContext, true);
477 }
478
479 void InspectorCanvasAgent::canvasChanged(CanvasBase& canvasBase, const FloatRect&)
480 {
481     auto* context = canvasBase.renderingContext();
482     if (!context)
483         return;
484
485     auto inspectorCanvas = findInspectorCanvas(*context);
486     ASSERT(inspectorCanvas);
487     if (!inspectorCanvas)
488         return;
489
490     inspectorCanvas->canvasChanged();
491 }
492
493 void InspectorCanvasAgent::canvasDestroyed(CanvasBase& canvasBase)
494 {
495     auto* context = canvasBase.renderingContext();
496     if (!context)
497         return;
498
499     auto inspectorCanvas = findInspectorCanvas(*context);
500     ASSERT(inspectorCanvas);
501     if (!inspectorCanvas)
502         return;
503
504     unbindCanvas(*inspectorCanvas);
505 }
506
507 void InspectorCanvasAgent::didFinishRecordingCanvasFrame(CanvasRenderingContext& context, bool forceDispatch)
508 {
509     if (!context.callTracingActive())
510         return;
511
512     auto inspectorCanvas = findInspectorCanvas(context);
513     ASSERT(inspectorCanvas);
514     if (!inspectorCanvas)
515         return;
516
517     if (!inspectorCanvas->hasRecordingData()) {
518         if (forceDispatch) {
519             m_frontendDispatcher->recordingFinished(inspectorCanvas->identifier(), nullptr);
520             inspectorCanvas->resetRecordingData();
521         }
522         return;
523     }
524
525     if (forceDispatch)
526         inspectorCanvas->markCurrentFrameIncomplete();
527
528     inspectorCanvas->finalizeFrame();
529     if (inspectorCanvas->currentFrameHasData())
530         m_frontendDispatcher->recordingProgress(inspectorCanvas->identifier(), inspectorCanvas->releaseFrames(), inspectorCanvas->bufferUsed());
531
532     if (!forceDispatch && !inspectorCanvas->overFrameCount())
533         return;
534
535     m_frontendDispatcher->recordingFinished(inspectorCanvas->identifier(), inspectorCanvas->releaseObjectForRecording());
536 }
537
538 void InspectorCanvasAgent::consoleStartRecordingCanvas(CanvasRenderingContext& context, JSC::ExecState& exec, JSC::JSObject* options)
539 {
540     auto inspectorCanvas = findInspectorCanvas(context);
541     ASSERT(inspectorCanvas);
542     if (!inspectorCanvas)
543         return;
544
545     RecordingOptions recordingOptions;
546     if (options) {
547         JSC::VM& vm = exec.vm();
548         if (JSC::JSValue optionSingleFrame = options->get(&exec, JSC::Identifier::fromString(vm, "singleFrame")))
549             recordingOptions.frameCount = optionSingleFrame.toBoolean(&exec) ? 1 : 0;
550         if (JSC::JSValue optionFrameCount = options->get(&exec, JSC::Identifier::fromString(vm, "frameCount")))
551             recordingOptions.frameCount = optionFrameCount.toNumber(&exec);
552         if (JSC::JSValue optionMemoryLimit = options->get(&exec, JSC::Identifier::fromString(vm, "memoryLimit")))
553             recordingOptions.memoryLimit = optionMemoryLimit.toNumber(&exec);
554         if (JSC::JSValue optionName = options->get(&exec, JSC::Identifier::fromString(vm, "name")))
555             recordingOptions.name = optionName.toWTFString(&exec);
556     }
557     startRecording(*inspectorCanvas, Inspector::Protocol::Recording::Initiator::Console, WTFMove(recordingOptions));
558 }
559
560 #if ENABLE(WEBGL)
561 void InspectorCanvasAgent::didEnableExtension(WebGLRenderingContextBase& context, const String& extension)
562 {
563     auto inspectorCanvas = findInspectorCanvas(context);
564     ASSERT(inspectorCanvas);
565     if (!inspectorCanvas)
566         return;
567
568     m_frontendDispatcher->extensionEnabled(inspectorCanvas->identifier(), extension);
569 }
570
571 void InspectorCanvasAgent::didCreateProgram(WebGLRenderingContextBase& context, WebGLProgram& program)
572 {
573     auto inspectorCanvas = findInspectorCanvas(context);
574     ASSERT(inspectorCanvas);
575     if (!inspectorCanvas)
576         return;
577
578     auto inspectorProgram = InspectorShaderProgram::create(program, *inspectorCanvas);
579     String programIdentifier = inspectorProgram->identifier();
580     m_identifierToInspectorProgram.set(programIdentifier, WTFMove(inspectorProgram));
581     m_frontendDispatcher->programCreated(inspectorCanvas->identifier(), programIdentifier);
582 }
583
584 void InspectorCanvasAgent::willDeleteProgram(WebGLProgram& program)
585 {
586     auto inspectorProgram = findInspectorProgram(program);
587     if (!inspectorProgram)
588         return;
589
590     String identifier = unbindProgram(*inspectorProgram);
591     m_frontendDispatcher->programDeleted(identifier);
592 }
593
594 bool InspectorCanvasAgent::isShaderProgramDisabled(WebGLProgram& program)
595 {
596     auto inspectorProgram = findInspectorProgram(program);
597     ASSERT(inspectorProgram);
598     if (!inspectorProgram)
599         return false;
600
601     return inspectorProgram->disabled();
602 }
603
604 bool InspectorCanvasAgent::isShaderProgramHighlighted(WebGLProgram& program)
605 {
606     auto inspectorProgram = findInspectorProgram(program);
607     ASSERT(inspectorProgram);
608     if (!inspectorProgram)
609         return false;
610
611     return inspectorProgram->highlighted();
612 }
613 #endif
614
615 #if ENABLE(WEBGPU)
616 void InspectorCanvasAgent::didCreateWebGPUDevice(WebGPUDevice& device)
617 {
618     if (findInspectorCanvas(device)) {
619         ASSERT_NOT_REACHED();
620         return;
621     }
622
623     bindCanvas(device, true);
624 }
625
626 void InspectorCanvasAgent::willDestroyWebGPUDevice(WebGPUDevice& device)
627 {
628     auto inspectorCanvas = findInspectorCanvas(device);
629     ASSERT(inspectorCanvas);
630     if (!inspectorCanvas)
631         return;
632
633     unbindCanvas(*inspectorCanvas);
634 }
635
636 void InspectorCanvasAgent::willConfigureSwapChain(GPUCanvasContext& contextGPU, WebGPUSwapChain& newSwapChain)
637 {
638     auto notifyDeviceForSwapChain = [&] (WebGPUSwapChain& webGPUSwapChain) {
639         for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
640             if (auto* device = inspectorCanvas->deviceContext()) {
641                 if (device->device().swapChain() == webGPUSwapChain.swapChain())
642                     m_frontendDispatcher->clientNodesChanged(inspectorCanvas->identifier());
643             }
644         }
645     };
646
647     if (auto* existingSwapChain = contextGPU.swapChain())
648         notifyDeviceForSwapChain(*existingSwapChain);
649
650     notifyDeviceForSwapChain(newSwapChain);
651 }
652 #endif
653
654 void InspectorCanvasAgent::startRecording(InspectorCanvas& inspectorCanvas, Inspector::Protocol::Recording::Initiator initiator, RecordingOptions&& recordingOptions)
655 {
656     auto* context = inspectorCanvas.canvasContext();
657     ASSERT(context);
658     // FIXME: <https://webkit.org/b/201651> Web Inspector: Canvas: support canvas recordings for WebGPUDevice
659
660     if (!is<CanvasRenderingContext2D>(context)
661         && !is<ImageBitmapRenderingContext>(context)
662 #if ENABLE(WEBGL)
663         && !is<WebGLRenderingContext>(context)
664 #endif
665 #if ENABLE(WEBGL2)
666         && !is<WebGL2RenderingContext>(context)
667 #endif
668     )
669         return;
670
671     if (context->callTracingActive())
672         return;
673
674     inspectorCanvas.resetRecordingData();
675     if (recordingOptions.frameCount)
676         inspectorCanvas.setFrameCount(recordingOptions.frameCount.value());
677     if (recordingOptions.memoryLimit)
678         inspectorCanvas.setBufferLimit(recordingOptions.memoryLimit.value());
679     if (recordingOptions.name)
680         inspectorCanvas.setRecordingName(recordingOptions.name.value());
681     context->setCallTracingActive(true);
682
683     m_frontendDispatcher->recordingStarted(inspectorCanvas.identifier(), initiator);
684 }
685
686 void InspectorCanvasAgent::canvasDestroyedTimerFired()
687 {
688     if (!m_removedCanvasIdentifiers.size())
689         return;
690
691     for (auto& identifier : m_removedCanvasIdentifiers)
692         m_frontendDispatcher->canvasRemoved(identifier);
693
694     m_removedCanvasIdentifiers.clear();
695 }
696
697 void InspectorCanvasAgent::clearCanvasData()
698 {
699     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
700         if (auto* context = inspectorCanvas->canvasContext())
701             context->canvasBase().removeObserver(*this);
702     }
703
704     m_identifierToInspectorCanvas.clear();
705 #if ENABLE(WEBGL)
706     m_identifierToInspectorProgram.clear();
707     m_removedCanvasIdentifiers.clear();
708 #endif
709
710     if (m_canvasDestroyedTimer.isActive())
711         m_canvasDestroyedTimer.stop();
712 }
713
714 InspectorCanvas& InspectorCanvasAgent::bindCanvas(CanvasRenderingContext& context, bool captureBacktrace)
715 {
716     auto inspectorCanvas = InspectorCanvas::create(context);
717     m_identifierToInspectorCanvas.set(inspectorCanvas->identifier(), inspectorCanvas.copyRef());
718
719     context.canvasBase().addObserver(*this);
720
721     m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(captureBacktrace));
722
723 #if ENABLE(WEBGL)
724     if (is<WebGLRenderingContextBase>(context)) {
725         auto& contextWebGL = downcast<WebGLRenderingContextBase>(context);
726         if (Optional<Vector<String>> extensions = contextWebGL.getSupportedExtensions()) {
727             for (const String& extension : *extensions) {
728                 if (contextWebGL.extensionIsEnabled(extension))
729                     m_frontendDispatcher->extensionEnabled(inspectorCanvas->identifier(), extension);
730             }
731         }
732     }
733 #endif
734
735     return inspectorCanvas;
736 }
737
738 #if ENABLE(WEBGPU)
739 InspectorCanvas& InspectorCanvasAgent::bindCanvas(WebGPUDevice& device, bool captureBacktrace)
740 {
741     auto inspectorCanvas = InspectorCanvas::create(device);
742     m_identifierToInspectorCanvas.set(inspectorCanvas->identifier(), inspectorCanvas.copyRef());
743
744     m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(captureBacktrace));
745
746     return inspectorCanvas;
747 }
748 #endif
749
750 void InspectorCanvasAgent::unbindCanvas(InspectorCanvas& inspectorCanvas)
751 {
752 #if ENABLE(WEBGL)
753     Vector<InspectorShaderProgram*> programsToRemove;
754     for (auto& inspectorProgram : m_identifierToInspectorProgram.values()) {
755         if (&inspectorProgram->canvas() == &inspectorCanvas)
756             programsToRemove.append(inspectorProgram.get());
757     }
758
759     for (auto* inspectorProgram : programsToRemove)
760         unbindProgram(*inspectorProgram);
761 #endif
762
763     if (auto* context = inspectorCanvas.canvasContext())
764         context->canvasBase().removeObserver(*this);
765
766     String identifier = inspectorCanvas.identifier();
767     m_identifierToInspectorCanvas.remove(identifier);
768
769     // This can be called in response to GC. Due to the single-process model used in WebKit1, the
770     // event must be dispatched from a timer to prevent the frontend from making JS allocations
771     // while the GC is still active.
772     m_removedCanvasIdentifiers.append(identifier);
773
774     if (!m_canvasDestroyedTimer.isActive())
775         m_canvasDestroyedTimer.startOneShot(0_s);
776 }
777
778 RefPtr<InspectorCanvas> InspectorCanvasAgent::assertInspectorCanvas(ErrorString& errorString, const String& canvasId)
779 {
780     auto inspectorCanvas = m_identifierToInspectorCanvas.get(canvasId);
781     if (!inspectorCanvas) {
782         errorString = "Missing canvas for given canvasId"_s;
783         return nullptr;
784     }
785     return inspectorCanvas;
786 }
787
788 RefPtr<InspectorCanvas> InspectorCanvasAgent::findInspectorCanvas(CanvasRenderingContext& context)
789 {
790     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
791         if (inspectorCanvas->canvasContext() == &context)
792             return inspectorCanvas;
793     }
794     return nullptr;
795 }
796
797 #if ENABLE(WEBGPU)
798 RefPtr<InspectorCanvas> InspectorCanvasAgent::findInspectorCanvas(WebGPUDevice& device)
799 {
800     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
801         if (inspectorCanvas->deviceContext() == &device)
802             return inspectorCanvas;
803     }
804     return nullptr;
805 }
806 #endif
807
808 #if ENABLE(WEBGL)
809 String InspectorCanvasAgent::unbindProgram(InspectorShaderProgram& inspectorProgram)
810 {
811     String identifier = inspectorProgram.identifier();
812     m_identifierToInspectorProgram.remove(identifier);
813
814     return identifier;
815 }
816
817 RefPtr<InspectorShaderProgram> InspectorCanvasAgent::assertInspectorProgram(ErrorString& errorString, const String& programId)
818 {
819     auto inspectorProgram = m_identifierToInspectorProgram.get(programId);
820     if (!inspectorProgram) {
821         errorString = "Missing program for given programId"_s;
822         return nullptr;
823     }
824     return inspectorProgram;
825 }
826
827 RefPtr<InspectorShaderProgram> InspectorCanvasAgent::findInspectorProgram(WebGLProgram& program)
828 {
829     for (auto& inspectorProgram : m_identifierToInspectorProgram.values()) {
830         if (&inspectorProgram->program() == &program)
831             return inspectorProgram;
832     }
833     return nullptr;
834 }
835 #endif
836
837 } // namespace WebCore