#include "config.h"
#include "core/html/HTMLCanvasElement.h"
-#include <math.h>
-#include "HTMLNames.h"
-#include "RuntimeEnabledFeatures.h"
#include "bindings/v8/ExceptionMessages.h"
#include "bindings/v8/ExceptionState.h"
#include "bindings/v8/ScriptController.h"
+#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
+#include "core/frame/LocalFrame.h"
+#include "core/frame/Settings.h"
#include "core/html/ImageData.h"
#include "core/html/canvas/Canvas2DContextAttributes.h"
#include "core/html/canvas/CanvasRenderingContext2D.h"
#include "core/html/canvas/WebGLContextAttributes.h"
+#include "core/html/canvas/WebGLContextEvent.h"
#include "core/html/canvas/WebGLRenderingContext.h"
-#include "core/frame/Frame.h"
-#include "core/frame/Settings.h"
#include "core/rendering/RenderHTMLCanvas.h"
#include "platform/MIMETypeRegistry.h"
+#include "platform/RuntimeEnabledFeatures.h"
#include "platform/graphics/Canvas2DImageBufferSurface.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "platform/graphics/ImageBuffer.h"
#include "platform/graphics/gpu/WebGLImageBufferSurface.h"
#include "platform/transforms/AffineTransform.h"
#include "public/platform/Platform.h"
+#include <math.h>
+#include <v8.h>
namespace WebCore {
//In Skia, we will also limit width/height to 32767.
static const int MaxSkiaDim = 32767; // Maximum width/height in CSS pixels.
-HTMLCanvasElement::HTMLCanvasElement(Document& document)
+DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(CanvasObserver);
+
+inline HTMLCanvasElement::HTMLCanvasElement(Document& document)
: HTMLElement(canvasTag, document)
+ , DocumentVisibilityObserver(document)
, m_size(DefaultWidth, DefaultHeight)
- , m_rendererIsCanvas(false)
, m_ignoreReset(false)
, m_accelerationDisabled(false)
, m_externallyAllocatedMemory(0)
ScriptWrappable::init(this);
}
-PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
-{
- return adoptRef(new HTMLCanvasElement(document));
-}
+DEFINE_NODE_FACTORY(HTMLCanvasElement)
HTMLCanvasElement::~HTMLCanvasElement()
{
- setExternallyAllocatedMemory(0);
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
+ v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-m_externallyAllocatedMemory);
+#if !ENABLE(OILPAN)
+ HashSet<RawPtr<CanvasObserver> >::iterator end = m_observers.end();
+ for (HashSet<RawPtr<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it)
(*it)->canvasDestroyed(this);
-
- m_context.clear(); // Ensure this goes away before the ImageBuffer.
+ // Ensure these go away before the ImageBuffer.
+ m_contextStateSaver.clear();
+ m_context.clear();
+#endif
}
void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style)
{
- Frame* frame = document().frame();
- if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) {
- m_rendererIsCanvas = true;
+ LocalFrame* frame = document().frame();
+ if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript))
return new RenderHTMLCanvas(this);
- }
-
- m_rendererIsCanvas = false;
return HTMLElement::createRenderer(style);
}
if (!m_context) {
blink::Platform::current()->histogramEnumeration("Canvas.ContextType", Context2d, ContextTypeCount);
m_context = CanvasRenderingContext2D::create(this, static_cast<Canvas2DContextAttributes*>(attrs), document().inQuirksMode());
- if (m_context)
- scheduleLayerUpdate();
+ setNeedsCompositingUpdate();
}
return m_context.get();
}
- Settings* settings = document().settings();
- if (settings && settings->webGLEnabled()) {
- // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
- // Now that WebGL is ratified, we will also accept "webgl" as the context name in Chrome.
- ContextType contextType;
- bool is3dContext = true;
- if (type == "webkit-3d")
- contextType = ContextWebkit3d;
- else if (type == "experimental-webgl")
- contextType = ContextExperimentalWebgl;
- else if (type == "webgl")
- contextType = ContextWebgl;
- else
- is3dContext = false;
-
- if (is3dContext) {
- if (m_context && !m_context->is3d())
- return 0;
- if (!m_context) {
- blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
- m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
- if (m_context)
- scheduleLayerUpdate();
- }
- return m_context.get();
+ // Accept the the provisional "experimental-webgl" or official "webgl" context ID.
+ ContextType contextType;
+ bool is3dContext = true;
+ if (type == "experimental-webgl")
+ contextType = ContextExperimentalWebgl;
+ else if (type == "webgl")
+ contextType = ContextWebgl;
+ else
+ is3dContext = false;
+
+ if (is3dContext) {
+ if (m_context && !m_context->is3d()) {
+ dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Canvas has an existing, non-WebGL context"));
+ return 0;
}
+ if (!m_context) {
+ blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
+ m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
+ setNeedsCompositingUpdate();
+ updateExternallyAllocatedMemory();
+ }
+ return m_context.get();
}
return 0;
}
return;
m_dirtyRect.unite(r);
- ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
+ ro->invalidatePaintRectangle(enclosingIntRect(m_dirtyRect));
}
notifyObserversCanvasChanged(rect);
void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
{
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
+ WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator end = m_observers.end();
+ for (WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it)
(*it)->canvasChanged(this, rect);
}
toWebGLRenderingContext(m_context.get())->reshape(width(), height());
if (RenderObject* renderer = this->renderer()) {
- if (m_rendererIsCanvas) {
+ if (renderer->isCanvas()) {
if (oldSize != size()) {
toRenderHTMLCanvas(renderer)->canvasSizeChanged();
if (renderBox() && renderBox()->hasAcceleratedCompositing())
renderBox()->contentChanged(CanvasChanged);
}
if (hadImageBuffer)
- renderer->repaint();
+ renderer->paintInvalidationForWholeRenderer();
}
}
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
+ WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator end = m_observers.end();
+ for (WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it)
(*it)->canvasResized(this);
}
}
-void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
+void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r)
{
// Clear the dirty rect
m_dirtyRect = FloatRect();
if (imageBuffer) {
CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy;
if (m_presentedImage)
- context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation, useLowQualityScale);
+ context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation);
else
- context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), compositeOperator, blink::WebBlendModeNormal, useLowQualityScale);
+ context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), 0, compositeOperator);
}
+ } else {
+ // When alpha is false, we should draw to opaque black.
+ if (m_context && !m_context->hasAlpha())
+ context->fillRect(FloatRect(r), Color(0, 0, 0));
}
if (is3D())
if (!m_presentedImage) {
// The buffer contains the last presented data, so save a copy of it.
m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
+ updateExternallyAllocatedMemory();
}
}
void HTMLCanvasElement::clearPresentationCopy()
{
m_presentedImage.clear();
+ updateExternallyAllocatedMemory();
}
void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
{
m_size = size;
m_didFailToCreateImageBuffer = false;
- m_contextStateSaver.clear();
- m_imageBuffer.clear();
- setExternallyAllocatedMemory(0);
+ discardImageBuffer();
clearCopiedImage();
+ if (m_context && m_context->is2d()) {
+ CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
+ if (context2d->isContextLost()) {
+ context2d->restoreContext();
+ }
+ }
}
String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
return lowercaseMimeType;
}
-String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState)
+const AtomicString HTMLCanvasElement::imageSourceURL() const
{
- if (!m_originClean) {
- exceptionState.throwSecurityError("Tainted canvases may not be exported.");
- return String();
- }
+ return AtomicString(toDataURLInternal("image/png", 0, true));
+}
+String HTMLCanvasElement::toDataURLInternal(const String& mimeType, const double* quality, bool isSaving) const
+{
if (m_size.isEmpty() || !buffer())
return String("data:,");
String encodingMimeType = toEncodingMimeType(mimeType);
// Try to get ImageData first, as that may avoid lossy conversions.
- RefPtr<ImageData> imageData = getImageData();
+ RefPtrWillBeRawPtr<ImageData> imageData = getImageData();
if (imageData)
return ImageDataToDataURL(ImageDataBuffer(imageData->size(), imageData->data()), encodingMimeType, quality);
- if (m_context)
+ if (m_context && m_context->is3d()) {
+ toWebGLRenderingContext(m_context.get())->setSavingImage(isSaving);
m_context->paintRenderingResultsToCanvas();
+ toWebGLRenderingContext(m_context.get())->setSavingImage(false);
+ }
return buffer()->toDataURL(encodingMimeType, quality);
}
-PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
+String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState) const
+{
+ if (!m_originClean) {
+ exceptionState.throwSecurityError("Tainted canvases may not be exported.");
+ return String();
+ }
+
+ return toDataURLInternal(mimeType, quality);
+}
+
+PassRefPtrWillBeRawPtr<ImageData> HTMLCanvasElement::getImageData() const
{
if (!m_context || !m_context->is3d())
- return 0;
+ return nullptr;
return toWebGLRenderingContext(m_context.get())->paintRenderingResultsToImageData();
}
void HTMLCanvasElement::createImageBuffer()
{
+ createImageBufferInternal();
+ if (m_didFailToCreateImageBuffer && m_context && m_context->is2d())
+ toCanvasRenderingContext2D(m_context.get())->loseContext();
+}
+
+void HTMLCanvasElement::createImageBufferInternal()
+{
ASSERT(!m_imageBuffer);
+ ASSERT(!m_contextStateSaver);
m_didFailToCreateImageBuffer = true;
m_didClearImageBuffer = true;
OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
if (!surface->isValid())
return;
+
m_imageBuffer = ImageBuffer::create(surface.release());
+ m_imageBuffer->setClient(this);
m_didFailToCreateImageBuffer = false;
- setExternallyAllocatedMemory(4 * width() * height());
+ updateExternallyAllocatedMemory();
if (is3D()) {
// Early out for WebGL canvases
- m_contextStateSaver.clear();
return;
}
+ m_imageBuffer->setClient(this);
m_imageBuffer->context()->setShouldClampToSourceRect(false);
- m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
+ m_imageBuffer->context()->disableAntialiasingOptimizationForHairlineImages();
+ m_imageBuffer->context()->setImageInterpolationQuality(CanvasDefaultInterpolationQuality);
// Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the
// rendering mode is accelerated or not. For consistency, we don't want to apply AA in accelerated
// canvases but not in unaccelerated canvases.
// See CanvasRenderingContext2D::State::State() for more information.
m_imageBuffer->context()->setMiterLimit(10);
m_imageBuffer->context()->setStrokeThickness(1);
+#if ASSERT_ENABLED
+ m_imageBuffer->context()->disableDestructionChecks(); // 2D canvas is allowed to leave context in an unfinalized state.
+#endif
m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
- // Recalculate compositing requirements if acceleration state changed.
if (m_context)
- scheduleLayerUpdate();
+ setNeedsCompositingUpdate();
+}
+
+void HTMLCanvasElement::notifySurfaceInvalid()
+{
+ if (m_context && m_context->is2d()) {
+ CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get());
+ context2d->loseContext();
+ }
+}
+
+void HTMLCanvasElement::trace(Visitor* visitor)
+{
+ visitor->trace(m_observers);
+ visitor->trace(m_context);
+ DocumentVisibilityObserver::trace(visitor);
+ HTMLElement::trace(visitor);
}
-void HTMLCanvasElement::setExternallyAllocatedMemory(intptr_t externallyAllocatedMemory)
+void HTMLCanvasElement::updateExternallyAllocatedMemory() const
{
+ int bufferCount = 0;
+ if (m_imageBuffer)
+ bufferCount++;
+ if (is3D())
+ bufferCount += 2;
+ if (m_copiedImage)
+ bufferCount++;
+ if (m_presentedImage)
+ bufferCount++;
+
+ Checked<intptr_t, RecordOverflow> checkedExternallyAllocatedMemory = 4 * bufferCount;
+ checkedExternallyAllocatedMemory *= width();
+ checkedExternallyAllocatedMemory *= height();
+ intptr_t externallyAllocatedMemory;
+ if (checkedExternallyAllocatedMemory.safeGet(externallyAllocatedMemory) == CheckedState::DidOverflow)
+ externallyAllocatedMemory = std::numeric_limits<intptr_t>::max();
+
+ // Subtracting two intptr_t that are known to be positive will never underflow.
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory);
m_externallyAllocatedMemory = externallyAllocatedMemory;
}
GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
{
- if (m_didFailToCreateImageBuffer) {
- ASSERT(!hasImageBuffer());
+ if (!hasImageBuffer())
return 0;
- }
return drawingContext();
}
{
if ((hasImageBuffer() && !m_imageBuffer->isAccelerated()) || m_didFailToCreateImageBuffer)
return;
- m_imageBuffer.clear();
+ discardImageBuffer();
OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
m_imageBuffer = ImageBuffer::create(size(), opacityMode);
m_didFailToCreateImageBuffer = !m_imageBuffer;
if (m_context)
m_context->paintRenderingResultsToCanvas();
m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
+ updateExternallyAllocatedMemory();
}
return m_copiedImage.get();
}
}
}
+void HTMLCanvasElement::discardImageBuffer()
+{
+ m_contextStateSaver.clear(); // uses context owned by m_imageBuffer
+ m_imageBuffer.clear();
+ updateExternallyAllocatedMemory();
+}
+
+bool HTMLCanvasElement::hasValidImageBuffer() const
+{
+ return m_imageBuffer && m_imageBuffer->isSurfaceValid();
+}
+
void HTMLCanvasElement::clearCopiedImage()
{
m_copiedImage.clear();
m_didClearImageBuffer = false;
+ updateExternallyAllocatedMemory();
}
AffineTransform HTMLCanvasElement::baseTransform() const
return m_imageBuffer->baseTransform();
}
+void HTMLCanvasElement::didChangeVisibilityState(PageVisibilityState visibility)
+{
+ if (hasImageBuffer()) {
+ bool hidden = visibility != PageVisibilityStateVisible;
+ if (hidden) {
+ clearCopiedImage();
+ if (is3D()) {
+ discardImageBuffer();
+ }
+ }
+ if (hasImageBuffer()) {
+ m_imageBuffer->setIsHidden(hidden);
+ }
+ }
+}
+
+void HTMLCanvasElement::didMoveToNewDocument(Document& oldDocument)
+{
+ setObservedDocument(document());
+ HTMLElement::didMoveToNewDocument(oldDocument);
+}
+
+PassRefPtr<Image> HTMLCanvasElement::getSourceImageForCanvas(SourceImageMode mode, SourceImageStatus* status) const
+{
+ if (!width() || !height()) {
+ *status = ZeroSizeCanvasSourceImageStatus;
+ return nullptr;
+ }
+
+ if (!buffer()) {
+ *status = InvalidSourceImageStatus;
+ return nullptr;
+ }
+
+ if (mode == CopySourceImageIfVolatile) {
+ *status = NormalSourceImageStatus;
+ return copiedImage();
+ }
+
+ if (m_context && m_context->is3d()) {
+ m_context->paintRenderingResultsToCanvas();
+ *status = ExternalSourceImageStatus;
+ } else {
+ *status = NormalSourceImageStatus;
+ }
+ return m_imageBuffer->copyImage(DontCopyBackingStore, Unscaled);
+}
+
+bool HTMLCanvasElement::wouldTaintOrigin(SecurityOrigin*) const
+{
+ return !originClean();
+}
+
+FloatSize HTMLCanvasElement::sourceSize() const
+{
+ return FloatSize(width(), height());
+}
+
}