Web Inspector: Instrument active pixel memory used by 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::didChangeCanvasMemory(HTMLCanvasElement& canvasElement)
184 {
185     CanvasEntry* canvasEntry = getCanvasEntry(canvasElement);
186     if (!canvasEntry)
187         return;
188
189     m_frontendDispatcher->canvasMemoryChanged(canvasEntry->identifier, canvasElement.memoryCost());
190 }
191
192 void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
193 {
194     auto it = m_canvasEntries.find(&canvasElement);
195     if (it == m_canvasEntries.end())
196         return;
197
198     String canvasIdentifier = it->value.identifier;
199     m_canvasEntries.remove(it);
200
201     if (!m_enabled)
202         return;
203
204     // WebCore::CanvasObserver::canvasDestroyed is called in response to the GC destroying the HTMLCanvasElement.
205     // Due to the single-process model used in WebKit1, the event must be dispatched from a timer to prevent
206     // the frontend from making JS allocations while the GC is still active.
207     m_removedCanvasIdentifiers.append(canvasIdentifier);
208
209     if (!m_timer.isActive())
210         m_timer.startOneShot(0_s);
211 }
212
213 void InspectorCanvasAgent::canvasDestroyedTimerFired()
214 {
215     if (!m_removedCanvasIdentifiers.size())
216         return;
217
218     for (const auto& identifier : m_removedCanvasIdentifiers)
219         m_frontendDispatcher->canvasRemoved(identifier);
220
221     m_removedCanvasIdentifiers.clear();
222 }
223
224 void InspectorCanvasAgent::clearCanvasData()
225 {
226     for (auto* canvasElement : m_canvasEntries.keys())
227         canvasElement->removeObserver(*this);
228
229     m_canvasEntries.clear();
230     m_canvasToCSSCanvasId.clear();
231     m_removedCanvasIdentifiers.clear();
232
233     if (m_timer.isActive())
234         m_timer.stop();
235 }
236
237 InspectorCanvasAgent::CanvasEntry* InspectorCanvasAgent::getCanvasEntry(HTMLCanvasElement& canvasElement)
238 {
239     auto findResult = m_canvasEntries.find(&canvasElement);
240     if (findResult != m_canvasEntries.end())
241         return &findResult->value;
242
243     return nullptr;
244 }
245
246 InspectorCanvasAgent::CanvasEntry* InspectorCanvasAgent::getCanvasEntry(const String& canvasIdentifier)
247 {
248     for (auto& canvasEntry : m_canvasEntries.values()) {
249         if (canvasEntry.identifier == canvasIdentifier)
250             return &canvasEntry;
251     }
252
253     return nullptr;
254 }
255
256 Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvasAgent::buildObjectForCanvas(const CanvasEntry& canvasEntry, HTMLCanvasElement& canvasElement)
257 {
258     Frame* frame = canvasElement.document().frame();
259     CanvasRenderingContext* context = canvasElement.renderingContext();
260
261     Inspector::Protocol::Canvas::ContextType contextType;
262     if (context->is2d())
263         contextType = Inspector::Protocol::Canvas::ContextType::Canvas2D;
264     else {
265         ASSERT(context->isWebGL());
266         contextType = Inspector::Protocol::Canvas::ContextType::WebGL;
267     }
268
269     auto canvas = Inspector::Protocol::Canvas::Canvas::create()
270         .setCanvasId(canvasEntry.identifier)
271         .setFrameId(m_pageAgent->frameId(frame))
272         .setContextType(contextType)
273         .release();
274
275     if (!canvasEntry.cssCanvasName.isEmpty())
276         canvas->setCssCanvasName(canvasEntry.cssCanvasName);
277     else {
278         InspectorDOMAgent* domAgent = m_instrumentingAgents.inspectorDOMAgent();
279         int nodeId = domAgent->boundNodeId(&canvasElement);
280         if (!nodeId) {
281             if (int documentNodeId = domAgent->boundNodeId(&canvasElement.document())) {
282                 ErrorString ignored;
283                 nodeId = domAgent->pushNodeToFrontend(ignored, documentNodeId, &canvasElement);
284             }
285         }
286
287         if (nodeId)
288             canvas->setNodeId(nodeId);
289     }
290
291 #if ENABLE(WEBGL)
292     if (is<WebGLRenderingContextBase>(context)) {
293         if (std::optional<WebGLContextAttributes> attributes = downcast<WebGLRenderingContextBase>(context)->getContextAttributes()) {
294             canvas->setContextAttributes(Inspector::Protocol::Canvas::ContextAttributes::create()
295                 .setAlpha(attributes->alpha)
296                 .setDepth(attributes->depth)
297                 .setStencil(attributes->stencil)
298                 .setAntialias(attributes->antialias)
299                 .setPremultipliedAlpha(attributes->premultipliedAlpha)
300                 .setPreserveDrawingBuffer(attributes->preserveDrawingBuffer)
301                 .setFailIfMajorPerformanceCaveat(attributes->failIfMajorPerformanceCaveat)
302                 .release());
303         }
304     }
305 #endif
306
307     if (size_t memoryCost = canvasElement.memoryCost())
308         canvas->setMemoryCost(memoryCost);
309
310     return canvas;
311 }
312
313 } // namespace WebCore