Web Inspector: Send context attributes for tracked canvases
[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 "Frame.h"
33 #include "InspectorDOMAgent.h"
34 #include "InspectorPageAgent.h"
35 #include "InstrumentingAgents.h"
36 #include "MainFrame.h"
37 #include "WebGLContextAttributes.h"
38 #include "WebGLRenderingContextBase.h"
39 #include <inspector/IdentifiersFactory.h>
40 #include <inspector/InspectorProtocolObjects.h>
41
42 using namespace Inspector;
43
44 namespace WebCore {
45
46 InspectorCanvasAgent::InspectorCanvasAgent(WebAgentContext& context, InspectorPageAgent* pageAgent)
47     : InspectorAgentBase(ASCIILiteral("Canvas"), context)
48     , m_frontendDispatcher(std::make_unique<Inspector::CanvasFrontendDispatcher>(context.frontendRouter))
49     , m_backendDispatcher(Inspector::CanvasBackendDispatcher::create(context.backendDispatcher, this))
50     , m_pageAgent(pageAgent)
51     , m_timer(*this, &InspectorCanvasAgent::canvasDestroyedTimerFired)
52 {
53 }
54
55 void InspectorCanvasAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
56 {
57 }
58
59 void InspectorCanvasAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
60 {
61     ErrorString ignored;
62     disable(ignored);
63 }
64
65 void InspectorCanvasAgent::discardAgent()
66 {
67     clearCanvasData();
68 }
69
70 void InspectorCanvasAgent::enable(ErrorString&)
71 {
72     if (m_enabled)
73         return;
74
75     m_enabled = true;
76
77     for (const auto& pair : m_canvasEntries) {
78         auto* canvasElement = pair.key;
79         auto& canvasEntry = pair.value;
80         m_frontendDispatcher->canvasAdded(buildObjectForCanvas(canvasEntry, *canvasElement));
81     }
82 }
83
84 void InspectorCanvasAgent::disable(ErrorString&)
85 {
86     if (!m_enabled)
87         return;
88
89     if (m_timer.isActive())
90         m_timer.stop();
91
92     m_removedCanvasIdentifiers.clear();
93
94     m_enabled = false;
95 }
96
97 void InspectorCanvasAgent::requestNode(ErrorString& errorString, const String& canvasId, int* nodeId)
98 {
99     const CanvasEntry* canvasEntry = getCanvasEntry(canvasId);
100     if (!canvasEntry) {
101         errorString = ASCIILiteral("Invalid canvas identifier");
102         return;
103     }
104
105     int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&canvasEntry->element->document());
106     if (!documentNodeId) {
107         errorString = ASCIILiteral("Document has not been requested");
108         return;
109     }
110
111     *nodeId = m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, canvasEntry->element);
112 }
113
114 void InspectorCanvasAgent::requestContent(ErrorString& errorString, const String& canvasId, String* content)
115 {
116     const CanvasEntry* canvasEntry = getCanvasEntry(canvasId);
117     if (!canvasEntry) {
118         errorString = ASCIILiteral("Invalid canvas identifier");
119         return;
120     }
121
122     CanvasRenderingContext* context = canvasEntry->element->renderingContext();
123     if (is<CanvasRenderingContext2D>(context)) {
124         ExceptionOr<String> result = canvasEntry->element->toDataURL(ASCIILiteral("image/png"));
125         if (result.hasException()) {
126             errorString = result.releaseException().releaseMessage();
127             return;
128         }
129         *content = result.releaseReturnValue();
130     } else {
131         // FIXME: <https://webkit.org/b/173569> Web Inspector: Support getting the content of WebGL/WebGL2/WebGPU contexts
132         errorString = ASCIILiteral("Unsupported canvas context type");
133     }
134 }
135
136 void InspectorCanvasAgent::frameNavigated(Frame& frame)
137 {
138     if (frame.isMainFrame()) {
139         clearCanvasData();
140         return;
141     }
142
143     Vector<HTMLCanvasElement*> canvasesForFrame;
144     for (const auto& canvasElement : m_canvasEntries.keys()) {
145         if (canvasElement->document().frame() == &frame)
146             canvasesForFrame.append(canvasElement);
147     }
148
149     for (auto* canvasElement : canvasesForFrame) {
150         auto canvasEntry = m_canvasEntries.take(canvasElement);
151         if (m_enabled)
152             m_frontendDispatcher->canvasRemoved(canvasEntry.identifier);
153     }
154 }
155
156 void InspectorCanvasAgent::didCreateCSSCanvas(HTMLCanvasElement& canvasElement, const String& name)
157 {
158     ASSERT(!m_canvasToCSSCanvasId.contains(&canvasElement));
159     ASSERT(!m_canvasEntries.contains(&canvasElement));
160
161     m_canvasToCSSCanvasId.set(&canvasElement, name);
162 }
163
164 void InspectorCanvasAgent::didCreateCanvasRenderingContext(HTMLCanvasElement& canvasElement)
165 {
166     if (m_canvasEntries.contains(&canvasElement)) {
167         ASSERT_NOT_REACHED();
168         return;
169     }
170
171     CanvasEntry newCanvasEntry("canvas:" + IdentifiersFactory::createIdentifier(), &canvasElement);
172     newCanvasEntry.cssCanvasName = m_canvasToCSSCanvasId.take(&canvasElement);
173
174     m_canvasEntries.set(&canvasElement, newCanvasEntry);
175     canvasElement.addObserver(*this);
176
177     if (!m_enabled)
178         return;
179
180     m_frontendDispatcher->canvasAdded(buildObjectForCanvas(newCanvasEntry, canvasElement));
181 }
182
183 void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
184 {
185     auto it = m_canvasEntries.find(&canvasElement);
186     if (it == m_canvasEntries.end())
187         return;
188
189     String canvasIdentifier = it->value.identifier;
190     m_canvasEntries.remove(it);
191
192     if (!m_enabled)
193         return;
194
195     // WebCore::CanvasObserver::canvasDestroyed is called in response to the GC destroying the HTMLCanvasElement.
196     // Due to the single-process model used in WebKit1, the event must be dispatched from a timer to prevent
197     // the frontend from making JS allocations while the GC is still active.
198     m_removedCanvasIdentifiers.append(canvasIdentifier);
199
200     if (!m_timer.isActive())
201         m_timer.startOneShot(0_s);
202 }
203
204 void InspectorCanvasAgent::canvasDestroyedTimerFired()
205 {
206     if (!m_removedCanvasIdentifiers.size())
207         return;
208
209     for (const auto& identifier : m_removedCanvasIdentifiers)
210         m_frontendDispatcher->canvasRemoved(identifier);
211
212     m_removedCanvasIdentifiers.clear();
213 }
214
215 void InspectorCanvasAgent::clearCanvasData()
216 {
217     for (auto* canvasElement : m_canvasEntries.keys())
218         canvasElement->removeObserver(*this);
219
220     m_canvasEntries.clear();
221     m_canvasToCSSCanvasId.clear();
222     m_removedCanvasIdentifiers.clear();
223
224     if (m_timer.isActive())
225         m_timer.stop();
226 }
227
228 InspectorCanvasAgent::CanvasEntry* InspectorCanvasAgent::getCanvasEntry(HTMLCanvasElement& canvasElement)
229 {
230     auto findResult = m_canvasEntries.find(&canvasElement);
231     if (findResult != m_canvasEntries.end())
232         return &findResult->value;
233
234     return nullptr;
235 }
236
237 InspectorCanvasAgent::CanvasEntry* InspectorCanvasAgent::getCanvasEntry(const String& canvasIdentifier)
238 {
239     for (auto& canvasEntry : m_canvasEntries.values()) {
240         if (canvasEntry.identifier == canvasIdentifier)
241             return &canvasEntry;
242     }
243
244     return nullptr;
245 }
246
247 Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvasAgent::buildObjectForCanvas(const CanvasEntry& canvasEntry, HTMLCanvasElement& canvasElement)
248 {
249     Frame* frame = canvasElement.document().frame();
250     CanvasRenderingContext* context = canvasElement.renderingContext();
251
252     Inspector::Protocol::Canvas::ContextType contextType;
253     if (context->is2d())
254         contextType = Inspector::Protocol::Canvas::ContextType::Canvas2D;
255     else {
256         ASSERT(context->isWebGL());
257         contextType = Inspector::Protocol::Canvas::ContextType::WebGL;
258     }
259
260     auto canvas = Inspector::Protocol::Canvas::Canvas::create()
261         .setCanvasId(canvasEntry.identifier)
262         .setFrameId(m_pageAgent->frameId(frame))
263         .setContextType(contextType)
264         .release();
265
266     if (!canvasEntry.cssCanvasName.isEmpty())
267         canvas->setCssCanvasName(canvasEntry.cssCanvasName);
268     else {
269         InspectorDOMAgent* domAgent = m_instrumentingAgents.inspectorDOMAgent();
270         int nodeId = domAgent->boundNodeId(&canvasElement);
271         if (!nodeId) {
272             if (int documentNodeId = domAgent->boundNodeId(&canvasElement.document())) {
273                 ErrorString ignored;
274                 nodeId = domAgent->pushNodeToFrontend(ignored, documentNodeId, &canvasElement);
275             }
276         }
277
278         if (nodeId)
279             canvas->setNodeId(nodeId);
280     }
281
282 #if ENABLE(WEBGL)
283     if (is<WebGLRenderingContextBase>(context)) {
284         if (std::optional<WebGLContextAttributes> attributes = downcast<WebGLRenderingContextBase>(context)->getContextAttributes()) {
285             canvas->setContextAttributes(Inspector::Protocol::Canvas::ContextAttributes::create()
286                 .setAlpha(attributes->alpha)
287                 .setDepth(attributes->depth)
288                 .setStencil(attributes->stencil)
289                 .setAntialias(attributes->antialias)
290                 .setPremultipliedAlpha(attributes->premultipliedAlpha)
291                 .setPreserveDrawingBuffer(attributes->preserveDrawingBuffer)
292                 .setFailIfMajorPerformanceCaveat(attributes->failIfMajorPerformanceCaveat)
293                 .release());
294         }
295     }
296 #endif
297
298     return canvas;
299 }
300
301 } // namespace WebCore