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