2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "InspectorCanvasAgent.h"
29 #include "CanvasRenderingContext.h"
30 #include "CanvasRenderingContext2D.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>
48 #include "JSWebGLRenderingContext.h"
52 #include "JSWebGL2RenderingContext.h"
56 #include "JSWebGPURenderingContext.h"
59 using namespace Inspector;
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)
73 void InspectorCanvasAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
77 void InspectorCanvasAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
83 void InspectorCanvasAgent::discardAgent()
88 void InspectorCanvasAgent::enable(ErrorString&)
95 for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
96 m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents));
99 void InspectorCanvasAgent::disable(ErrorString&)
104 if (m_canvasDestroyedTimer.isActive())
105 m_canvasDestroyedTimer.stop();
107 m_removedCanvasIdentifiers.clear();
109 if (m_canvasRecordingTimer.isActive())
110 m_canvasRecordingTimer.stop();
112 for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
113 inspectorCanvas->resetRecordingData();
118 void InspectorCanvasAgent::requestNode(ErrorString& errorString, const String& canvasId, int* nodeId)
120 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
121 if (!inspectorCanvas)
124 int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&inspectorCanvas->canvas().document());
125 if (!documentNodeId) {
126 errorString = ASCIILiteral("Document has not been requested");
130 *nodeId = m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, &inspectorCanvas->canvas());
133 void InspectorCanvasAgent::requestContent(ErrorString& errorString, const String& canvasId, String* content)
135 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
136 if (!inspectorCanvas)
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();
146 *content = result.releaseReturnValue().string;
149 else if (is<WebGLRenderingContextBase>(context)) {
150 WebGLRenderingContextBase* gl = downcast<WebGLRenderingContextBase>(context);
152 gl->setPreventBufferClearForInspector(true);
153 auto result = inspectorCanvas->canvas().toDataURL(ASCIILiteral("image/png"));
154 gl->setPreventBufferClearForInspector(false);
156 if (result.hasException()) {
157 errorString = result.releaseException().releaseMessage();
160 *content = result.releaseReturnValue().string;
163 // FIXME: <https://webkit.org/b/173621> Web Inspector: Support getting the content of WebGPU contexts
165 errorString = ASCIILiteral("Unsupported canvas context type");
168 void InspectorCanvasAgent::requestCSSCanvasClientNodes(ErrorString& errorString, const String& canvasId, RefPtr<Inspector::Protocol::Array<int>>& result)
170 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
171 if (!inspectorCanvas)
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));
181 static JSC::JSValue contextAsScriptValue(JSC::ExecState& state, CanvasRenderingContext* context)
183 JSC::JSLockHolder lock(&state);
185 if (is<CanvasRenderingContext2D>(context))
186 return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<CanvasRenderingContext2D>(context));
188 if (is<WebGLRenderingContext>(context))
189 return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGLRenderingContext>(context));
192 if (is<WebGL2RenderingContext>(context))
193 return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGL2RenderingContext>(context));
196 if (is<WebGPURenderingContext>(context))
197 return toJS(&state, deprecatedGlobalObjectForPrototype(&state), downcast<WebGPURenderingContext>(context));
203 void InspectorCanvasAgent::resolveCanvasContext(ErrorString& errorString, const String& canvasId, const String* const objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>& result)
205 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
206 if (!inspectorCanvas)
209 Frame* frame = inspectorCanvas->canvas().document().frame();
211 errorString = ASCIILiteral("Canvas belongs to a document without a frame");
215 auto& state = *mainWorldExecState(frame);
216 auto injectedScript = m_injectedScriptManager.injectedScriptFor(&state);
217 ASSERT(!injectedScript.hasNoValue());
219 CanvasRenderingContext* context = inspectorCanvas->canvas().renderingContext();
220 JSC::JSValue value = contextAsScriptValue(state, context);
222 ASSERT_NOT_REACHED();
223 errorString = ASCIILiteral("Unknown context type");
227 String objectGroupName = objectGroup ? *objectGroup : String();
228 result = injectedScript.wrapObject(value, objectGroupName);
231 void InspectorCanvasAgent::requestRecording(ErrorString& errorString, const String& canvasId, const bool* const singleFrame, const int* const memoryLimit)
233 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
234 if (!inspectorCanvas)
237 if (inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
238 errorString = ASCIILiteral("Already recording canvas");
242 inspectorCanvas->resetRecordingData();
244 inspectorCanvas->setSingleFrame(*singleFrame);
246 inspectorCanvas->setBufferLimit(*memoryLimit);
248 inspectorCanvas->canvas().renderingContext()->setCallTracingActive(true);
251 void InspectorCanvasAgent::cancelRecording(ErrorString& errorString, const String& canvasId)
253 auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
254 if (!inspectorCanvas)
257 if (!inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
258 errorString = ASCIILiteral("No active recording for canvas");
262 didFinishRecordingCanvasFrame(inspectorCanvas->canvas(), true);
265 void InspectorCanvasAgent::frameNavigated(Frame& frame)
267 if (frame.isMainFrame()) {
272 Vector<InspectorCanvas*> inspectorCanvases;
273 for (RefPtr<InspectorCanvas>& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
274 if (inspectorCanvas->canvas().document().frame() == &frame)
275 inspectorCanvases.append(inspectorCanvas.get());
278 for (auto* inspectorCanvas : inspectorCanvases) {
279 String identifier = unbindCanvas(*inspectorCanvas);
281 m_frontendDispatcher->canvasRemoved(identifier);
285 void InspectorCanvasAgent::didCreateCSSCanvas(HTMLCanvasElement& canvasElement, const String& name)
287 if (findInspectorCanvas(canvasElement)) {
288 ASSERT_NOT_REACHED();
292 ASSERT(!m_canvasToCSSCanvasName.contains(&canvasElement));
293 m_canvasToCSSCanvasName.set(&canvasElement, name);
296 void InspectorCanvasAgent::didChangeCSSCanvasClientNodes(HTMLCanvasElement& canvasElement)
298 auto* inspectorCanvas = findInspectorCanvas(canvasElement);
299 if (!inspectorCanvas)
302 m_frontendDispatcher->cssCanvasClientNodesChanged(inspectorCanvas->identifier());
305 void InspectorCanvasAgent::didCreateCanvasRenderingContext(HTMLCanvasElement& canvasElement)
307 if (findInspectorCanvas(canvasElement)) {
308 ASSERT_NOT_REACHED();
312 canvasElement.addObserver(*this);
314 String cssCanvasName = m_canvasToCSSCanvasName.take(&canvasElement);
315 auto inspectorCanvas = InspectorCanvas::create(canvasElement, cssCanvasName);
318 m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents));
320 m_identifierToInspectorCanvas.set(inspectorCanvas->identifier(), WTFMove(inspectorCanvas));
323 void InspectorCanvasAgent::didChangeCanvasMemory(HTMLCanvasElement& canvasElement)
325 auto* inspectorCanvas = findInspectorCanvas(canvasElement);
326 if (!inspectorCanvas)
329 m_frontendDispatcher->canvasMemoryChanged(inspectorCanvas->identifier(), canvasElement.memoryCost());
332 void InspectorCanvasAgent::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<RecordCanvasActionVariant>&& parameters)
334 HTMLCanvasElement& canvasElement = canvasRenderingContext.canvas();
336 auto* inspectorCanvas = findInspectorCanvas(canvasElement);
337 ASSERT(inspectorCanvas);
338 if (!inspectorCanvas)
341 ASSERT(canvasRenderingContext.callTracingActive());
342 if (!canvasRenderingContext.callTracingActive())
345 inspectorCanvas->recordAction(name, WTFMove(parameters));
347 if (!m_canvasRecordingTimer.isActive())
348 m_canvasRecordingTimer.startOneShot(0_s);
350 if (!inspectorCanvas->hasBufferSpace())
351 didFinishRecordingCanvasFrame(canvasElement, true);
354 void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
356 auto* inspectorCanvas = findInspectorCanvas(canvasElement);
357 ASSERT(inspectorCanvas);
358 if (!inspectorCanvas)
361 String identifier = unbindCanvas(*inspectorCanvas);
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);
370 if (!m_canvasDestroyedTimer.isActive())
371 m_canvasDestroyedTimer.startOneShot(0_s);
374 void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canvasElement, bool forceDispatch)
376 auto* inspectorCanvas = findInspectorCanvas(canvasElement);
377 ASSERT(inspectorCanvas);
378 if (!inspectorCanvas)
381 CanvasRenderingContext* canvasRenderingContext = inspectorCanvas->canvas().renderingContext();
382 ASSERT(canvasRenderingContext->callTracingActive());
383 if (!canvasRenderingContext->callTracingActive())
386 if (!inspectorCanvas->hasRecordingData())
389 if (!forceDispatch && !inspectorCanvas->singleFrame()) {
390 inspectorCanvas->markNewFrame();
395 inspectorCanvas->markCurrentFrameIncomplete();
397 // <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext
399 Inspector::Protocol::Recording::Type type;
400 if (is<CanvasRenderingContext2D>(canvasRenderingContext))
401 type = Inspector::Protocol::Recording::Type::Canvas2D;
403 ASSERT_NOT_REACHED();
404 type = Inspector::Protocol::Recording::Type::Canvas2D;
407 auto recording = Inspector::Protocol::Recording::Recording::create()
410 .setInitialState(inspectorCanvas->releaseInitialState())
411 .setFrames(inspectorCanvas->releaseFrames())
412 .setData(inspectorCanvas->releaseData())
415 m_frontendDispatcher->recordingFinished(inspectorCanvas->identifier(), WTFMove(recording));
417 inspectorCanvas->resetRecordingData();
420 void InspectorCanvasAgent::canvasDestroyedTimerFired()
422 if (!m_removedCanvasIdentifiers.size())
426 for (auto& identifier : m_removedCanvasIdentifiers)
427 m_frontendDispatcher->canvasRemoved(identifier);
430 m_removedCanvasIdentifiers.clear();
433 void InspectorCanvasAgent::canvasRecordingTimerFired()
435 for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
436 if (!inspectorCanvas->canvas().renderingContext()->callTracingActive())
439 didFinishRecordingCanvasFrame(inspectorCanvas->canvas());
443 void InspectorCanvasAgent::clearCanvasData()
445 for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
446 inspectorCanvas->canvas().removeObserver(*this);
448 m_identifierToInspectorCanvas.clear();
449 m_canvasToCSSCanvasName.clear();
450 m_removedCanvasIdentifiers.clear();
452 if (m_canvasRecordingTimer.isActive())
453 m_canvasRecordingTimer.stop();
455 if (m_canvasDestroyedTimer.isActive())
456 m_canvasDestroyedTimer.stop();
459 String InspectorCanvasAgent::unbindCanvas(InspectorCanvas& inspectorCanvas)
461 ASSERT(!m_canvasToCSSCanvasName.contains(&inspectorCanvas.canvas()));
463 String identifier = inspectorCanvas.identifier();
464 m_identifierToInspectorCanvas.remove(identifier);
469 InspectorCanvas* InspectorCanvasAgent::assertInspectorCanvas(ErrorString& errorString, const String& identifier)
471 RefPtr<InspectorCanvas> inspectorCanvas = m_identifierToInspectorCanvas.get(identifier);
472 if (!inspectorCanvas) {
473 errorString = ASCIILiteral("No canvas for given identifier.");
477 return inspectorCanvas.get();
480 InspectorCanvas* InspectorCanvasAgent::findInspectorCanvas(HTMLCanvasElement& canvasElement)
482 for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
483 if (&inspectorCanvas->canvas() == &canvasElement)
484 return inspectorCanvas.get();
490 } // namespace WebCore