1/*
2 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "HTMLCanvasElement.h"
30
31#include "Blob.h"
32#include "BlobCallback.h"
33#include "CanvasGradient.h"
34#include "CanvasPattern.h"
35#include "CanvasRenderingContext2D.h"
36#include "Document.h"
37#include "Frame.h"
38#include "FrameLoaderClient.h"
39#include "GPUBasedCanvasRenderingContext.h"
40#include "GeometryUtilities.h"
41#include "GraphicsContext.h"
42#include "HTMLNames.h"
43#include "HTMLParserIdioms.h"
44#include "ImageBitmapRenderingContext.h"
45#include "ImageBuffer.h"
46#include "ImageData.h"
47#include "InspectorInstrumentation.h"
48#include "JSDOMConvertDictionary.h"
49#include "MIMETypeRegistry.h"
50#include "RenderElement.h"
51#include "RenderHTMLCanvas.h"
52#include "ResourceLoadObserver.h"
53#include "RuntimeEnabledFeatures.h"
54#include "ScriptController.h"
55#include "Settings.h"
56#include "StringAdaptors.h"
57#include <JavaScriptCore/JSCInlines.h>
58#include <JavaScriptCore/JSLock.h>
59#include <math.h>
60#include <wtf/IsoMallocInlines.h>
61#include <wtf/RAMSize.h>
62#include <wtf/text/StringBuilder.h>
63
64#if ENABLE(MEDIA_STREAM)
65#include "CanvasCaptureMediaStreamTrack.h"
66#include "MediaStream.h"
67#endif
68
69#if ENABLE(WEBGL)
70#include "WebGLContextAttributes.h"
71#include "WebGLRenderingContext.h"
72#endif
73
74#if ENABLE(WEBGL2)
75#include "WebGL2RenderingContext.h"
76#endif
77
78#if ENABLE(WEBGPU)
79#include "GPUCanvasContext.h"
80#endif
81
82#if PLATFORM(COCOA)
83#include "MediaSampleAVFObjC.h"
84#include <pal/cf/CoreMediaSoftLink.h>
85#endif
86
87namespace WebCore {
88
89WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLCanvasElement);
90
91using namespace PAL;
92using namespace HTMLNames;
93
94// These values come from the WhatWG/W3C HTML spec.
95const int defaultWidth = 300;
96const int defaultHeight = 150;
97
98// Firefox limits width/height to 32767 pixels, but slows down dramatically before it
99// reaches that limit. We limit by area instead, giving us larger maximum dimensions,
100// in exchange for a smaller maximum canvas size. The maximum canvas size is in device pixels.
101#if PLATFORM(IOS_FAMILY)
102const unsigned maxCanvasArea = 4096 * 4096;
103#else
104const unsigned maxCanvasArea = 16384 * 16384;
105#endif
106
107#if USE(CG)
108// FIXME: It seems strange that the default quality is not the one that is literally named "default".
109// Should fix names to make this easier to understand, or write an excellent comment here explaining why not.
110const InterpolationQuality defaultInterpolationQuality = InterpolationLow;
111#else
112const InterpolationQuality defaultInterpolationQuality = InterpolationDefault;
113#endif
114
115static size_t activePixelMemory = 0;
116
117HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document& document)
118 : HTMLElement(tagName, document)
119 , m_size(defaultWidth, defaultHeight)
120{
121 ASSERT(hasTagName(canvasTag));
122}
123
124Ref<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
125{
126 return adoptRef(*new HTMLCanvasElement(canvasTag, document));
127}
128
129Ref<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document& document)
130{
131 return adoptRef(*new HTMLCanvasElement(tagName, document));
132}
133
134static void removeFromActivePixelMemory(size_t pixelsReleased)
135{
136 if (!pixelsReleased)
137 return;
138
139 if (pixelsReleased < activePixelMemory)
140 activePixelMemory -= pixelsReleased;
141 else
142 activePixelMemory = 0;
143}
144
145HTMLCanvasElement::~HTMLCanvasElement()
146{
147 notifyObserversCanvasDestroyed();
148
149 m_context = nullptr; // Ensure this goes away before the ImageBuffer.
150
151 releaseImageBufferAndContext();
152}
153
154void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
155{
156 if (name == widthAttr || name == heightAttr)
157 reset();
158 HTMLElement::parseAttribute(name, value);
159}
160
161RenderPtr<RenderElement> HTMLCanvasElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
162{
163 RefPtr<Frame> frame = document().frame();
164 if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript))
165 return createRenderer<RenderHTMLCanvas>(*this, WTFMove(style));
166 return HTMLElement::createElementRenderer(WTFMove(style), insertionPosition);
167}
168
169bool HTMLCanvasElement::canContainRangeEndPoint() const
170{
171 return false;
172}
173
174bool HTMLCanvasElement::canStartSelection() const
175{
176 return false;
177}
178
179ExceptionOr<void> HTMLCanvasElement::setHeight(unsigned value)
180{
181 if (m_context && m_context->isPlaceholder())
182 return Exception { InvalidStateError };
183 setAttributeWithoutSynchronization(heightAttr, AtomicString::number(limitToOnlyHTMLNonNegative(value, defaultHeight)));
184 return { };
185}
186
187ExceptionOr<void> HTMLCanvasElement::setWidth(unsigned value)
188{
189 if (m_context && m_context->isPlaceholder())
190 return Exception { InvalidStateError };
191 setAttributeWithoutSynchronization(widthAttr, AtomicString::number(limitToOnlyHTMLNonNegative(value, defaultWidth)));
192 return { };
193}
194
195static inline size_t maxActivePixelMemory()
196{
197 static size_t maxPixelMemory;
198 static std::once_flag onceFlag;
199 std::call_once(onceFlag, [] {
200#if PLATFORM(IOS_FAMILY)
201 maxPixelMemory = ramSize() / 4;
202#else
203 maxPixelMemory = std::max(ramSize() / 4, 2151 * MB);
204#endif
205 });
206 return maxPixelMemory;
207}
208
209ExceptionOr<Optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::ExecState& state, const String& contextId, Vector<JSC::Strong<JSC::Unknown>>&& arguments)
210{
211 if (m_context) {
212 if (m_context->isPlaceholder())
213 return Exception { InvalidStateError };
214
215 if (m_context->is2d()) {
216 if (!is2dType(contextId))
217 return Optional<RenderingContext> { WTF::nullopt };
218 return Optional<RenderingContext> { RefPtr<CanvasRenderingContext2D> { &downcast<CanvasRenderingContext2D>(*m_context) } };
219 }
220
221 if (m_context->isBitmapRenderer()) {
222 if (!isBitmapRendererType(contextId))
223 return Optional<RenderingContext> { WTF::nullopt };
224 return Optional<RenderingContext> { RefPtr<ImageBitmapRenderingContext> { &downcast<ImageBitmapRenderingContext>(*m_context) } };
225 }
226
227#if ENABLE(WEBGL)
228 if (m_context->isWebGL()) {
229 if (!isWebGLType(contextId))
230 return Optional<RenderingContext> { WTF::nullopt };
231 if (is<WebGLRenderingContext>(*m_context))
232 return Optional<RenderingContext> { RefPtr<WebGLRenderingContext> { &downcast<WebGLRenderingContext>(*m_context) } };
233#if ENABLE(WEBGL2)
234 ASSERT(is<WebGL2RenderingContext>(*m_context));
235 return Optional<RenderingContext> { RefPtr<WebGL2RenderingContext> { &downcast<WebGL2RenderingContext>(*m_context) } };
236#endif
237 }
238#endif
239
240#if ENABLE(WEBGPU)
241 if (m_context->isWebGPU()) {
242 if (!isWebGPUType(contextId))
243 return Optional<RenderingContext> { WTF::nullopt };
244 return Optional<RenderingContext> { RefPtr<GPUCanvasContext> { &downcast<GPUCanvasContext>(*m_context) } };
245 }
246#endif
247
248 ASSERT_NOT_REACHED();
249 return Optional<RenderingContext> { WTF::nullopt };
250 }
251
252 if (is2dType(contextId)) {
253 auto context = createContext2d(contextId);
254 if (!context)
255 return Optional<RenderingContext> { WTF::nullopt };
256 return Optional<RenderingContext> { RefPtr<CanvasRenderingContext2D> { context } };
257 }
258
259 if (isBitmapRendererType(contextId)) {
260 auto scope = DECLARE_THROW_SCOPE(state.vm());
261 auto attributes = convert<IDLDictionary<ImageBitmapRenderingContextSettings>>(state, !arguments.isEmpty() ? arguments[0].get() : JSC::jsUndefined());
262 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
263
264 auto context = createContextBitmapRenderer(contextId, WTFMove(attributes));
265 if (!context)
266 return Optional<RenderingContext> { WTF::nullopt };
267 return Optional<RenderingContext> { RefPtr<ImageBitmapRenderingContext> { context } };
268 }
269
270#if ENABLE(WEBGL)
271 if (isWebGLType(contextId)) {
272 auto scope = DECLARE_THROW_SCOPE(state.vm());
273 auto attributes = convert<IDLDictionary<WebGLContextAttributes>>(state, !arguments.isEmpty() ? arguments[0].get() : JSC::jsUndefined());
274 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
275
276 auto context = createContextWebGL(contextId, WTFMove(attributes));
277 if (!context)
278 return Optional<RenderingContext> { WTF::nullopt };
279
280 if (is<WebGLRenderingContext>(*context))
281 return Optional<RenderingContext> { RefPtr<WebGLRenderingContext> { &downcast<WebGLRenderingContext>(*context) } };
282#if ENABLE(WEBGL2)
283 ASSERT(is<WebGL2RenderingContext>(*context));
284 return Optional<RenderingContext> { RefPtr<WebGL2RenderingContext> { &downcast<WebGL2RenderingContext>(*context) } };
285#endif
286 }
287#endif
288
289#if ENABLE(WEBGPU)
290 if (isWebGPUType(contextId)) {
291 auto context = createContextWebGPU(contextId);
292 if (!context)
293 return Optional<RenderingContext> { WTF::nullopt };
294 return Optional<RenderingContext> { RefPtr<GPUCanvasContext> { context } };
295 }
296#endif
297
298 return Optional<RenderingContext> { WTF::nullopt };
299}
300
301CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type)
302{
303 if (HTMLCanvasElement::is2dType(type))
304 return getContext2d(type);
305
306 if (HTMLCanvasElement::isBitmapRendererType(type))
307 return getContextBitmapRenderer(type);
308
309#if ENABLE(WEBGL)
310 if (HTMLCanvasElement::isWebGLType(type))
311 return getContextWebGL(type);
312#endif
313
314#if ENABLE(WEBGPU)
315 if (HTMLCanvasElement::isWebGPUType(type))
316 return getContextWebGPU(type);
317#endif
318
319 return nullptr;
320}
321
322bool HTMLCanvasElement::is2dType(const String& type)
323{
324 return type == "2d";
325}
326
327CanvasRenderingContext2D* HTMLCanvasElement::createContext2d(const String& type)
328{
329 ASSERT_UNUSED(HTMLCanvasElement::is2dType(type), type);
330 ASSERT(!m_context);
331
332 bool usesDashboardCompatibilityMode = false;
333#if ENABLE(DASHBOARD_SUPPORT)
334 usesDashboardCompatibilityMode = document().settings().usesDashboardBackwardCompatibilityMode();
335#endif
336
337 // Make sure we don't use more pixel memory than the system can support.
338 size_t requestedPixelMemory = 4 * width() * height();
339 if (activePixelMemory + requestedPixelMemory > maxActivePixelMemory()) {
340 StringBuilder stringBuilder;
341 stringBuilder.appendLiteral("Total canvas memory use exceeds the maximum limit (");
342 stringBuilder.appendNumber(maxActivePixelMemory() / 1024 / 1024);
343 stringBuilder.appendLiteral(" MB).");
344 document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, stringBuilder.toString());
345 return nullptr;
346 }
347
348 m_context = CanvasRenderingContext2D::create(*this, document().inQuirksMode(), usesDashboardCompatibilityMode);
349
350 downcast<CanvasRenderingContext2D>(*m_context).setUsesDisplayListDrawing(m_usesDisplayListDrawing);
351 downcast<CanvasRenderingContext2D>(*m_context).setTracksDisplayListReplay(m_tracksDisplayListReplay);
352
353#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
354 // Need to make sure a RenderLayer and compositing layer get created for the Canvas.
355 invalidateStyleAndLayerComposition();
356#endif
357
358 return static_cast<CanvasRenderingContext2D*>(m_context.get());
359}
360
361CanvasRenderingContext2D* HTMLCanvasElement::getContext2d(const String& type)
362{
363 ASSERT_UNUSED(HTMLCanvasElement::is2dType(type), type);
364
365 if (m_context && !m_context->is2d())
366 return nullptr;
367
368 if (!m_context)
369 return createContext2d(type);
370 return static_cast<CanvasRenderingContext2D*>(m_context.get());
371}
372
373#if ENABLE(WEBGL)
374
375static bool requiresAcceleratedCompositingForWebGL()
376{
377#if PLATFORM(GTK) || PLATFORM(WIN_CAIRO)
378 return false;
379#else
380 return true;
381#endif
382
383}
384static bool shouldEnableWebGL(const Settings& settings)
385{
386 if (!settings.webGLEnabled())
387 return false;
388
389 if (!requiresAcceleratedCompositingForWebGL())
390 return true;
391
392 return settings.acceleratedCompositingEnabled();
393}
394
395bool HTMLCanvasElement::isWebGLType(const String& type)
396{
397 // Retain support for the legacy "webkit-3d" name.
398 return type == "webgl" || type == "experimental-webgl"
399#if ENABLE(WEBGL2)
400 || type == "webgl2"
401#endif
402 || type == "webkit-3d";
403}
404
405WebGLRenderingContextBase* HTMLCanvasElement::createContextWebGL(const String& type, WebGLContextAttributes&& attrs)
406{
407 ASSERT(HTMLCanvasElement::isWebGLType(type));
408 ASSERT(!m_context);
409
410 if (!shouldEnableWebGL(document().settings()))
411 return nullptr;
412
413 m_context = WebGLRenderingContextBase::create(*this, attrs, type);
414 if (m_context) {
415 // Need to make sure a RenderLayer and compositing layer get created for the Canvas.
416 invalidateStyleAndLayerComposition();
417 }
418
419 return downcast<WebGLRenderingContextBase>(m_context.get());
420}
421
422WebGLRenderingContextBase* HTMLCanvasElement::getContextWebGL(const String& type, WebGLContextAttributes&& attrs)
423{
424 ASSERT(HTMLCanvasElement::isWebGLType(type));
425
426 if (!shouldEnableWebGL(document().settings()))
427 return nullptr;
428
429 if (m_context && !m_context->isWebGL())
430 return nullptr;
431
432 if (!m_context)
433 return createContextWebGL(type, WTFMove(attrs));
434 return &downcast<WebGLRenderingContextBase>(*m_context);
435}
436
437#endif // ENABLE(WEBGL)
438
439#if ENABLE(WEBGPU)
440
441bool HTMLCanvasElement::isWebGPUType(const String& type)
442{
443 return type == "gpu";
444}
445
446GPUCanvasContext* HTMLCanvasElement::createContextWebGPU(const String& type)
447{
448 ASSERT_UNUSED(type, HTMLCanvasElement::isWebGPUType(type));
449 ASSERT(!m_context);
450
451 if (!RuntimeEnabledFeatures::sharedFeatures().webGPUEnabled())
452 return nullptr;
453
454 m_context = GPUCanvasContext::create(*this);
455 if (m_context) {
456 // Need to make sure a RenderLayer and compositing layer get created for the Canvas.
457 invalidateStyleAndLayerComposition();
458 }
459
460 return static_cast<GPUCanvasContext*>(m_context.get());
461}
462
463GPUCanvasContext* HTMLCanvasElement::getContextWebGPU(const String& type)
464{
465 ASSERT_UNUSED(type, HTMLCanvasElement::isWebGPUType(type));
466
467 if (!RuntimeEnabledFeatures::sharedFeatures().webGPUEnabled())
468 return nullptr;
469
470 if (m_context && !m_context->isWebGPU())
471 return nullptr;
472
473 if (!m_context)
474 return createContextWebGPU(type);
475 return static_cast<GPUCanvasContext*>(m_context.get());
476}
477
478#endif // ENABLE(WEBGPU)
479
480bool HTMLCanvasElement::isBitmapRendererType(const String& type)
481{
482 return type == "bitmaprenderer";
483}
484
485ImageBitmapRenderingContext* HTMLCanvasElement::createContextBitmapRenderer(const String& type, ImageBitmapRenderingContextSettings&& settings)
486{
487 ASSERT_UNUSED(type, HTMLCanvasElement::isBitmapRendererType(type));
488 ASSERT(!m_context);
489
490 m_context = ImageBitmapRenderingContext::create(*this, WTFMove(settings));
491
492#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
493 // Need to make sure a RenderLayer and compositing layer get created for the Canvas.
494 invalidateStyleAndLayerComposition();
495#endif
496
497 return static_cast<ImageBitmapRenderingContext*>(m_context.get());
498}
499
500ImageBitmapRenderingContext* HTMLCanvasElement::getContextBitmapRenderer(const String& type, ImageBitmapRenderingContextSettings&& settings)
501{
502 ASSERT_UNUSED(type, HTMLCanvasElement::isBitmapRendererType(type));
503 if (!m_context)
504 return createContextBitmapRenderer(type, WTFMove(settings));
505 return static_cast<ImageBitmapRenderingContext*>(m_context.get());
506}
507
508void HTMLCanvasElement::didDraw(const FloatRect& rect)
509{
510 clearCopiedImage();
511
512 FloatRect dirtyRect = rect;
513 if (auto* renderer = renderBox()) {
514 FloatRect destRect;
515 if (is<RenderReplaced>(renderer))
516 destRect = downcast<RenderReplaced>(renderer)->replacedContentRect();
517 else
518 destRect = renderer->contentBoxRect();
519
520 // Inflate dirty rect to cover antialiasing on image buffers.
521 if (drawingContext() && drawingContext()->shouldAntialias())
522 dirtyRect.inflate(1);
523
524 FloatRect r = mapRect(dirtyRect, FloatRect(0, 0, size().width(), size().height()), destRect);
525 r.intersect(destRect);
526
527 if (!r.isEmpty() && !m_dirtyRect.contains(r)) {
528 m_dirtyRect.unite(r);
529 renderer->repaintRectangle(enclosingIntRect(m_dirtyRect));
530 }
531 }
532 notifyObserversCanvasChanged(dirtyRect);
533}
534
535void HTMLCanvasElement::reset()
536{
537 if (m_ignoreReset)
538 return;
539
540 bool hadImageBuffer = hasCreatedImageBuffer();
541
542 int w = limitToOnlyHTMLNonNegative(attributeWithoutSynchronization(widthAttr), defaultWidth);
543 int h = limitToOnlyHTMLNonNegative(attributeWithoutSynchronization(heightAttr), defaultHeight);
544
545 if (m_contextStateSaver) {
546 // Reset to the initial graphics context state.
547 m_contextStateSaver->restore();
548 m_contextStateSaver->save();
549 }
550
551 if (is<CanvasRenderingContext2D>(m_context.get()))
552 downcast<CanvasRenderingContext2D>(*m_context).reset();
553
554 IntSize oldSize = size();
555 IntSize newSize(w, h);
556 // If the size of an existing buffer matches, we can just clear it instead of reallocating.
557 // This optimization is only done for 2D canvases for now.
558 if (m_hasCreatedImageBuffer && oldSize == newSize && m_context && m_context->is2d()) {
559 if (!m_didClearImageBuffer)
560 clearImageBuffer();
561 return;
562 }
563
564 setSurfaceSize(newSize);
565
566 if (isGPUBased() && oldSize != size())
567 downcast<GPUBasedCanvasRenderingContext>(*m_context).reshape(width(), height());
568
569 auto renderer = this->renderer();
570 if (is<RenderHTMLCanvas>(renderer)) {
571 auto& canvasRenderer = downcast<RenderHTMLCanvas>(*renderer);
572 if (oldSize != size()) {
573 canvasRenderer.canvasSizeChanged();
574 if (canvasRenderer.hasAcceleratedCompositing())
575 canvasRenderer.contentChanged(CanvasChanged);
576 }
577 if (hadImageBuffer)
578 canvasRenderer.repaint();
579 }
580
581 notifyObserversCanvasResized();
582}
583
584bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
585{
586 ASSERT(m_context);
587#if USE(IOSURFACE_CANVAS_BACKING_STORE)
588 if (m_context->is2d() || m_context->isBitmapRenderer())
589 return true;
590#endif
591
592 if (!m_context->isAccelerated())
593 return true;
594
595 if (renderBox() && renderBox()->hasAcceleratedCompositing())
596 return false;
597
598 return true;
599}
600
601
602void HTMLCanvasElement::paint(GraphicsContext& context, const LayoutRect& r)
603{
604 // Clear the dirty rect
605 m_dirtyRect = FloatRect();
606
607 if (!context.paintingDisabled()) {
608 bool shouldPaint = true;
609
610 if (m_context) {
611 shouldPaint = paintsIntoCanvasBuffer() || document().printing();
612 if (shouldPaint)
613 m_context->paintRenderingResultsToCanvas();
614 }
615
616 if (shouldPaint) {
617 if (hasCreatedImageBuffer()) {
618 ImageBuffer* imageBuffer = buffer();
619 if (imageBuffer) {
620 if (m_presentedImage) {
621 ImageOrientationDescription orientationDescription;
622#if ENABLE(CSS_IMAGE_ORIENTATION)
623 orientationDescription.setImageOrientationEnum(renderer()->style().imageOrientation());
624#endif
625 context.drawImage(*m_presentedImage, snappedIntRect(r), ImagePaintingOptions(orientationDescription));
626 } else
627 context.drawImageBuffer(*imageBuffer, snappedIntRect(r));
628 }
629 }
630
631 if (isGPUBased())
632 downcast<GPUBasedCanvasRenderingContext>(*m_context).markLayerComposited();
633 }
634 }
635
636 if (UNLIKELY(m_context && m_context->callTracingActive()))
637 InspectorInstrumentation::didFinishRecordingCanvasFrame(*m_context);
638}
639
640bool HTMLCanvasElement::isGPUBased() const
641{
642 return m_context && m_context->isGPUBased();
643}
644
645void HTMLCanvasElement::makeRenderingResultsAvailable()
646{
647 if (m_context)
648 m_context->paintRenderingResultsToCanvas();
649}
650
651void HTMLCanvasElement::makePresentationCopy()
652{
653 if (!m_presentedImage) {
654 // The buffer contains the last presented data, so save a copy of it.
655 m_presentedImage = buffer()->copyImage(CopyBackingStore, PreserveResolution::Yes);
656 }
657}
658
659void HTMLCanvasElement::clearPresentationCopy()
660{
661 m_presentedImage = nullptr;
662}
663
664void HTMLCanvasElement::releaseImageBufferAndContext()
665{
666 m_contextStateSaver = nullptr;
667 setImageBuffer(nullptr);
668}
669
670void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
671{
672 m_size = size;
673 m_hasCreatedImageBuffer = false;
674 releaseImageBufferAndContext();
675 clearCopiedImage();
676}
677
678static String toEncodingMimeType(const String& mimeType)
679{
680 if (!MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType))
681 return "image/png"_s;
682 return mimeType.convertToASCIILowercase();
683}
684
685// https://html.spec.whatwg.org/multipage/canvas.html#a-serialisation-of-the-bitmap-as-a-file
686static Optional<double> qualityFromJSValue(JSC::JSValue qualityValue)
687{
688 if (!qualityValue.isNumber())
689 return WTF::nullopt;
690
691 double qualityNumber = qualityValue.asNumber();
692 if (qualityNumber < 0 || qualityNumber > 1)
693 return WTF::nullopt;
694
695 return qualityNumber;
696}
697
698ExceptionOr<UncachedString> HTMLCanvasElement::toDataURL(const String& mimeType, JSC::JSValue qualityValue)
699{
700 if (!originClean())
701 return Exception { SecurityError };
702
703 if (m_size.isEmpty() || !buffer())
704 return UncachedString { "data:,"_s };
705 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
706 ResourceLoadObserver::shared().logCanvasRead(document());
707
708 auto encodingMIMEType = toEncodingMimeType(mimeType);
709 auto quality = qualityFromJSValue(qualityValue);
710
711#if USE(CG)
712 // Try to get ImageData first, as that may avoid lossy conversions.
713 if (auto imageData = getImageData())
714 return UncachedString { dataURL(*imageData, encodingMIMEType, quality) };
715#endif
716
717 makeRenderingResultsAvailable();
718
719 return UncachedString { buffer()->toDataURL(encodingMIMEType, quality) };
720}
721
722ExceptionOr<UncachedString> HTMLCanvasElement::toDataURL(const String& mimeType)
723{
724 return toDataURL(mimeType, { });
725}
726
727ExceptionOr<void> HTMLCanvasElement::toBlob(ScriptExecutionContext& context, Ref<BlobCallback>&& callback, const String& mimeType, JSC::JSValue qualityValue)
728{
729 if (!originClean())
730 return Exception { SecurityError };
731
732 if (m_size.isEmpty() || !buffer()) {
733 callback->scheduleCallback(context, nullptr);
734 return { };
735 }
736 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
737 ResourceLoadObserver::shared().logCanvasRead(document());
738
739 auto encodingMIMEType = toEncodingMimeType(mimeType);
740 auto quality = qualityFromJSValue(qualityValue);
741
742#if USE(CG)
743 if (auto imageData = getImageData()) {
744 RefPtr<Blob> blob;
745 Vector<uint8_t> blobData = data(*imageData, encodingMIMEType, quality);
746 if (!blobData.isEmpty())
747 blob = Blob::create(WTFMove(blobData), encodingMIMEType);
748 callback->scheduleCallback(context, WTFMove(blob));
749 return { };
750 }
751#endif
752
753 makeRenderingResultsAvailable();
754
755 RefPtr<Blob> blob;
756 Vector<uint8_t> blobData = buffer()->toData(encodingMIMEType, quality);
757 if (!blobData.isEmpty())
758 blob = Blob::create(WTFMove(blobData), encodingMIMEType);
759 callback->scheduleCallback(context, WTFMove(blob));
760 return { };
761}
762
763RefPtr<ImageData> HTMLCanvasElement::getImageData()
764{
765#if ENABLE(WEBGL)
766 if (is<WebGLRenderingContextBase>(m_context.get())) {
767 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
768 ResourceLoadObserver::shared().logCanvasRead(document());
769 return downcast<WebGLRenderingContextBase>(*m_context).paintRenderingResultsToImageData();
770 }
771#endif
772 return nullptr;
773}
774
775#if ENABLE(MEDIA_STREAM)
776
777RefPtr<MediaSample> HTMLCanvasElement::toMediaSample()
778{
779 auto* imageBuffer = buffer();
780 if (!imageBuffer)
781 return nullptr;
782 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
783 ResourceLoadObserver::shared().logCanvasRead(document());
784
785#if PLATFORM(COCOA)
786 makeRenderingResultsAvailable();
787 return MediaSampleAVFObjC::createImageSample(imageBuffer->toBGRAData(), width(), height());
788#else
789 return nullptr;
790#endif
791}
792
793ExceptionOr<Ref<MediaStream>> HTMLCanvasElement::captureStream(ScriptExecutionContext& context, Optional<double>&& frameRequestRate)
794{
795 if (!originClean())
796 return Exception(SecurityError, "Canvas is tainted"_s);
797 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
798 ResourceLoadObserver::shared().logCanvasRead(document());
799
800 if (frameRequestRate && frameRequestRate.value() < 0)
801 return Exception(NotSupportedError, "frameRequestRate is negative"_s);
802
803 auto track = CanvasCaptureMediaStreamTrack::create(context, *this, WTFMove(frameRequestRate));
804 auto stream = MediaStream::create(context);
805 stream->addTrack(track);
806 return stream;
807}
808#endif
809
810SecurityOrigin* HTMLCanvasElement::securityOrigin() const
811{
812 return &document().securityOrigin();
813}
814
815bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
816{
817 auto& settings = document().settings();
818
819 auto area = size.area<RecordOverflow>();
820 if (area.hasOverflowed())
821 return false;
822
823 if (area > settings.maximumAccelerated2dCanvasSize())
824 return false;
825
826#if USE(IOSURFACE_CANVAS_BACKING_STORE)
827 return settings.canvasUsesAcceleratedDrawing();
828#elif ENABLE(ACCELERATED_2D_CANVAS)
829 if (m_context && !m_context->is2d())
830 return false;
831
832 if (!settings.accelerated2dCanvasEnabled())
833 return false;
834
835 if (area < settings.minimumAccelerated2dCanvasSize())
836 return false;
837
838 return true;
839#else
840 UNUSED_PARAM(size);
841 return false;
842#endif
843}
844
845size_t HTMLCanvasElement::memoryCost() const
846{
847 // memoryCost() may be invoked concurrently from a GC thread, and we need to be careful
848 // about what data we access here and how. We need to hold a lock to prevent m_imageBuffer
849 // from being changed while we access it.
850 auto locker = holdLock(m_imageBufferAssignmentLock);
851 if (!m_imageBuffer)
852 return 0;
853 return m_imageBuffer->memoryCost();
854}
855
856size_t HTMLCanvasElement::externalMemoryCost() const
857{
858 // externalMemoryCost() may be invoked concurrently from a GC thread, and we need to be careful
859 // about what data we access here and how. We need to hold a lock to prevent m_imageBuffer
860 // from being changed while we access it.
861 auto locker = holdLock(m_imageBufferAssignmentLock);
862 if (!m_imageBuffer)
863 return 0;
864 return m_imageBuffer->externalMemoryCost();
865}
866
867void HTMLCanvasElement::setUsesDisplayListDrawing(bool usesDisplayListDrawing)
868{
869 if (usesDisplayListDrawing == m_usesDisplayListDrawing)
870 return;
871
872 m_usesDisplayListDrawing = usesDisplayListDrawing;
873
874 if (is<CanvasRenderingContext2D>(m_context.get()))
875 downcast<CanvasRenderingContext2D>(*m_context).setUsesDisplayListDrawing(m_usesDisplayListDrawing);
876}
877
878void HTMLCanvasElement::setTracksDisplayListReplay(bool tracksDisplayListReplay)
879{
880 if (tracksDisplayListReplay == m_tracksDisplayListReplay)
881 return;
882
883 m_tracksDisplayListReplay = tracksDisplayListReplay;
884
885 if (is<CanvasRenderingContext2D>(m_context.get()))
886 downcast<CanvasRenderingContext2D>(*m_context).setTracksDisplayListReplay(m_tracksDisplayListReplay);
887}
888
889String HTMLCanvasElement::displayListAsText(DisplayList::AsTextFlags flags) const
890{
891 if (is<CanvasRenderingContext2D>(m_context.get()))
892 return downcast<CanvasRenderingContext2D>(*m_context).displayListAsText(flags);
893
894 return String();
895}
896
897String HTMLCanvasElement::replayDisplayListAsText(DisplayList::AsTextFlags flags) const
898{
899 if (is<CanvasRenderingContext2D>(m_context.get()))
900 return downcast<CanvasRenderingContext2D>(*m_context).replayDisplayListAsText(flags);
901
902 return String();
903}
904
905void HTMLCanvasElement::createImageBuffer() const
906{
907 ASSERT(!m_imageBuffer);
908
909 m_hasCreatedImageBuffer = true;
910 m_didClearImageBuffer = true;
911
912 // Perform multiplication as floating point to avoid overflow
913 if (float(width()) * height() > maxCanvasArea) {
914 StringBuilder stringBuilder;
915 stringBuilder.appendLiteral("Canvas area exceeds the maximum limit (width * height > ");
916 stringBuilder.appendNumber(maxCanvasArea);
917 stringBuilder.appendLiteral(").");
918 document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, stringBuilder.toString());
919 return;
920 }
921
922 // Make sure we don't use more pixel memory than the system can support.
923 size_t requestedPixelMemory = 4 * width() * height();
924 if (activePixelMemory + requestedPixelMemory > maxActivePixelMemory()) {
925 StringBuilder stringBuilder;
926 stringBuilder.appendLiteral("Total canvas memory use exceeds the maximum limit (");
927 stringBuilder.appendNumber(maxActivePixelMemory() / 1024 / 1024);
928 stringBuilder.appendLiteral(" MB).");
929 document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, stringBuilder.toString());
930 return;
931 }
932
933 if (!width() || !height())
934 return;
935
936 RenderingMode renderingMode = shouldAccelerate(size()) ? Accelerated : Unaccelerated;
937
938 auto hostWindow = (document().view() && document().view()->root()) ? document().view()->root()->hostWindow() : nullptr;
939 setImageBuffer(ImageBuffer::create(size(), renderingMode, 1, ColorSpaceSRGB, hostWindow));
940}
941
942void HTMLCanvasElement::setImageBuffer(std::unique_ptr<ImageBuffer>&& buffer) const
943{
944 size_t previousMemoryCost = memoryCost();
945 removeFromActivePixelMemory(previousMemoryCost);
946
947 {
948 auto locker = holdLock(m_imageBufferAssignmentLock);
949 m_contextStateSaver = nullptr;
950 m_imageBuffer = WTFMove(buffer);
951 }
952
953 if (m_imageBuffer && m_size != m_imageBuffer->internalSize())
954 m_size = m_imageBuffer->internalSize();
955
956 size_t currentMemoryCost = memoryCost();
957 activePixelMemory += currentMemoryCost;
958
959 if (m_context && m_imageBuffer && previousMemoryCost != currentMemoryCost)
960 InspectorInstrumentation::didChangeCanvasMemory(*m_context);
961
962 if (!m_imageBuffer)
963 return;
964 m_imageBuffer->context().setShadowsIgnoreTransforms(true);
965 m_imageBuffer->context().setImageInterpolationQuality(defaultInterpolationQuality);
966 m_imageBuffer->context().setStrokeThickness(1);
967 m_contextStateSaver = std::make_unique<GraphicsContextStateSaver>(m_imageBuffer->context());
968
969 JSC::JSLockHolder lock(HTMLElement::scriptExecutionContext()->vm());
970 HTMLElement::scriptExecutionContext()->vm().heap.reportExtraMemoryAllocated(memoryCost());
971
972#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
973 if (m_context && m_context->is2d()) {
974 // Recalculate compositing requirements if acceleration state changed.
975 const_cast<HTMLCanvasElement*>(this)->invalidateStyleAndLayerComposition();
976 }
977#endif
978}
979
980void HTMLCanvasElement::setImageBufferAndMarkDirty(std::unique_ptr<ImageBuffer>&& buffer)
981{
982 m_hasCreatedImageBuffer = true;
983 setImageBuffer(WTFMove(buffer));
984 didDraw(FloatRect(FloatPoint(), m_size));
985}
986
987GraphicsContext* HTMLCanvasElement::drawingContext() const
988{
989 if (m_context && !m_context->is2d())
990 return nullptr;
991
992 return buffer() ? &m_imageBuffer->context() : nullptr;
993}
994
995GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
996{
997 if (!m_hasCreatedImageBuffer)
998 return nullptr;
999
1000 return drawingContext();
1001}
1002
1003ImageBuffer* HTMLCanvasElement::buffer() const
1004{
1005 if (!m_hasCreatedImageBuffer)
1006 createImageBuffer();
1007 return m_imageBuffer.get();
1008}
1009
1010Image* HTMLCanvasElement::copiedImage() const
1011{
1012 if (!m_copiedImage && buffer()) {
1013 if (m_context)
1014 m_context->paintRenderingResultsToCanvas();
1015 m_copiedImage = buffer()->copyImage(CopyBackingStore, PreserveResolution::Yes);
1016 }
1017 return m_copiedImage.get();
1018}
1019
1020void HTMLCanvasElement::clearImageBuffer() const
1021{
1022 ASSERT(m_hasCreatedImageBuffer);
1023 ASSERT(!m_didClearImageBuffer);
1024 ASSERT(m_context);
1025
1026 m_didClearImageBuffer = true;
1027
1028 if (is<CanvasRenderingContext2D>(*m_context)) {
1029 // No need to undo transforms/clip/etc. because we are called right after the context is reset.
1030 downcast<CanvasRenderingContext2D>(*m_context).clearRect(0, 0, width(), height());
1031 }
1032}
1033
1034void HTMLCanvasElement::clearCopiedImage()
1035{
1036 m_copiedImage = nullptr;
1037 m_didClearImageBuffer = false;
1038}
1039
1040AffineTransform HTMLCanvasElement::baseTransform() const
1041{
1042 ASSERT(m_hasCreatedImageBuffer);
1043 return m_imageBuffer->baseTransform();
1044}
1045
1046}
1047