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