#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 "bindings/core/v8/ExceptionMessages.h"
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ScriptController.h"
+#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/frame/LocalFrame.h"
#include "core/html/canvas/WebGLContextEvent.h"
#include "core/html/canvas/WebGLRenderingContext.h"
#include "core/rendering/RenderHTMLCanvas.h"
+#include "core/rendering/RenderLayer.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/RecordingImageBufferSurface.h"
+#include "platform/graphics/StaticBitmapImage.h"
#include "platform/graphics/UnacceleratedImageBufferSurface.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 {
+namespace blink {
using namespace HTMLNames;
//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)
, m_didFailToCreateImageBuffer(false)
, m_didClearImageBuffer(false)
{
- ScriptWrappable::init(this);
}
-PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
-{
- return adoptRef(new HTMLCanvasElement(document));
-}
+DEFINE_NODE_FACTORY(HTMLCanvasElement)
HTMLCanvasElement::~HTMLCanvasElement()
{
+ resetDirtyRect();
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-m_externallyAllocatedMemory);
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
- (*it)->canvasDestroyed(this);
-
- m_context.clear(); // Ensure this goes away before the ImageBuffer.
+#if !ENABLE(OILPAN)
+ for (CanvasObserver* canvasObserver : m_observers)
+ canvasObserver->canvasDestroyed(this);
+ // 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)
{
LocalFrame* frame = document().frame();
- if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) {
- m_rendererIsCanvas = true;
+ if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript))
return new RenderHTMLCanvas(this);
- }
-
- m_rendererIsCanvas = false;
return HTMLElement::createRenderer(style);
}
// before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
// context with any other type string will destroy any existing context.
enum ContextType {
- Context2d,
- ContextWebkit3d,
- ContextExperimentalWebgl,
- ContextWebgl,
- // Only add new items to the end and keep the order of existing items.
+ // Do not change assigned numbers of existing items: add new features to the end of the list.
+ Context2d = 0,
+ ContextExperimentalWebgl = 2,
+ ContextWebgl = 3,
ContextTypeCount,
};
// once it is created.
if (type == "2d") {
if (m_context && !m_context->is2d())
- return 0;
+ return nullptr;
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();
+
+ m_context = CanvasRenderingContext2D::create(this, static_cast<Canvas2DContextAttributes*>(attrs), document());
+ setNeedsCompositingUpdate();
}
return m_context.get();
}
- // 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()) {
- dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Canvas has an existing, non-WebGL context"));
- return 0;
- }
+ // Accept the the provisional "experimental-webgl" or official "webgl" context ID.
+ if (type == "webgl" || type == "experimental-webgl") {
+ ContextType contextType = (type == "webgl") ? ContextWebgl : ContextExperimentalWebgl;
if (!m_context) {
blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
- if (m_context) {
- scheduleLayerUpdate();
- updateExternallyAllocatedMemory();
- }
+ setNeedsCompositingUpdate();
+ updateExternallyAllocatedMemory();
+ } else if (!m_context->is3d()) {
+ dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Canvas has an existing, non-WebGL context"));
+ return nullptr;
}
return m_context.get();
}
- return 0;
+
+ return nullptr;
}
void HTMLCanvasElement::didDraw(const FloatRect& rect)
{
+ if (rect.isEmpty())
+ return;
clearCopiedImage();
+ if (m_dirtyRect.isEmpty())
+ blink::Platform::current()->currentThread()->addTaskObserver(this);
+ m_dirtyRect.unite(rect);
+}
+void HTMLCanvasElement::didFinalizeFrame()
+{
+ if (m_dirtyRect.isEmpty())
+ return;
+
+ // Propagate the m_dirtyRect accumulated so far to the compositor
+ // before restarting with a blank dirty rect.
+ FloatRect srcRect(0, 0, size().width(), size().height());
+ m_dirtyRect.intersect(srcRect);
if (RenderBox* ro = renderBox()) {
- FloatRect destRect = ro->contentBoxRect();
- FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
- r.intersect(destRect);
- if (r.isEmpty() || m_dirtyRect.contains(r))
- return;
+ FloatRect mappedDirtyRect = mapRect(m_dirtyRect, srcRect, ro->contentBoxRect());
+ // For querying RenderLayer::compositingState()
+ // FIXME: is this invalidation using the correct compositing state?
+ DisableCompositingQueryAsserts disabler;
+ ro->invalidatePaintRectangle(enclosingIntRect(mappedDirtyRect));
+ }
+ notifyObserversCanvasChanged(m_dirtyRect);
+ blink::Platform::current()->currentThread()->removeTaskObserver(this);
+ m_dirtyRect = FloatRect();
+}
+
+void HTMLCanvasElement::resetDirtyRect()
+{
+ if (m_dirtyRect.isEmpty())
+ return;
+ blink::Platform::current()->currentThread()->removeTaskObserver(this);
+ m_dirtyRect = FloatRect();
+}
- m_dirtyRect.unite(r);
- ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
+void HTMLCanvasElement::didProcessTask()
+{
+ // This method gets invoked if didDraw was called earlier in the current task.
+ ASSERT(!m_dirtyRect.isEmpty());
+ if (is3D()) {
+ didFinalizeFrame();
+ } else {
+ ASSERT(hasImageBuffer());
+ m_imageBuffer->finalizeFrame(m_dirtyRect);
}
+ ASSERT(m_dirtyRect.isEmpty());
+}
- notifyObserversCanvasChanged(rect);
+void HTMLCanvasElement::willProcessTask()
+{
+ ASSERT_NOT_REACHED();
}
void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
{
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
- (*it)->canvasChanged(this, rect);
+ for (CanvasObserver* canvasObserver : m_observers)
+ canvasObserver->canvasChanged(this, rect);
}
void HTMLCanvasElement::reset()
if (m_ignoreReset)
return;
+ resetDirtyRect();
+
bool ok;
bool hadImageBuffer = hasImageBuffer();
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->setShouldDoFullPaintInvalidation();
}
}
- HashSet<CanvasObserver*>::iterator end = m_observers.end();
- for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
- (*it)->canvasResized(this);
+ for (CanvasObserver* canvasObserver : m_observers)
+ canvasObserver->canvasResized(this);
}
bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r)
{
- // Clear the dirty rect
- m_dirtyRect = FloatRect();
-
- if (context->paintingDisabled())
- return;
-
if (m_context) {
if (!paintsIntoCanvasBuffer() && !document().printing())
return;
if (m_presentedImage)
context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation);
else
- context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), compositeOperator, blink::WebBlendModeNormal);
+ 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())
m_didFailToCreateImageBuffer = false;
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 nullptr;
return true;
}
+class UnacceleratedSurfaceFactory : public RecordingImageBufferFallbackSurfaceFactory {
+public:
+ virtual PassOwnPtr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode opacityMode)
+ {
+ return adoptPtr(new UnacceleratedImageBufferSurface(size, opacityMode));
+ }
+
+ virtual ~UnacceleratedSurfaceFactory() { }
+};
+
+class Accelerated2dSurfaceFactory : public RecordingImageBufferFallbackSurfaceFactory {
+public:
+ Accelerated2dSurfaceFactory(int msaaSampleCount) : m_msaaSampleCount(msaaSampleCount) { }
+
+ virtual PassOwnPtr<ImageBufferSurface> createSurface(const IntSize& size, OpacityMode opacityMode)
+ {
+ OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size, opacityMode, m_msaaSampleCount));
+ if (surface->isValid())
+ return surface.release();
+ return adoptPtr(new UnacceleratedImageBufferSurface(size, opacityMode));
+ }
+
+ virtual ~Accelerated2dSurfaceFactory() { }
+private:
+ int m_msaaSampleCount;
+};
+
+PassOwnPtr<RecordingImageBufferFallbackSurfaceFactory> HTMLCanvasElement::createSurfaceFactory(const IntSize& deviceSize, int* msaaSampleCount) const
+{
+ *msaaSampleCount = 0;
+ OwnPtr<RecordingImageBufferFallbackSurfaceFactory> surfaceFactory;
+ if (shouldAccelerate(deviceSize)) {
+ if (document().settings())
+ *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount();
+ surfaceFactory = adoptPtr(new Accelerated2dSurfaceFactory(*msaaSampleCount));
+ } else {
+ surfaceFactory = adoptPtr(new UnacceleratedSurfaceFactory());
+ }
+ return surfaceFactory.release();
+}
+
+bool HTMLCanvasElement::shouldUseDisplayList(const IntSize& deviceSize)
+{
+ if (RuntimeEnabledFeatures::forceDisplayList2dCanvasEnabled())
+ return true;
+
+ if (!RuntimeEnabledFeatures::displayList2dCanvasEnabled())
+ return false;
+
+ if (shouldAccelerate(deviceSize))
+ return false;
+
+ return true;
+}
+
PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount)
{
OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
*msaaSampleCount = 0;
- if (is3D())
- return adoptPtr(new WebGLImageBufferSurface(size(), opacityMode));
+ if (is3D()) {
+ // If 3d, but the use of the canvas will be for non-accelerated content
+ // (such as -webkit-canvas, then then make a non-accelerated
+ // ImageBuffer. This means copying the internal Image will require a
+ // pixel readback, but that is unavoidable in this case.
+ // FIXME: Actually, avoid setting m_accelerationDisabled at all when
+ // doing GPU-based rasterization.
+ if (m_accelerationDisabled)
+ return adoptPtr(new UnacceleratedImageBufferSurface(deviceSize, opacityMode));
+ return adoptPtr(new WebGLImageBufferSurface(deviceSize, opacityMode));
+ }
- if (shouldAccelerate(deviceSize)) {
- if (document().settings())
- *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount();
- OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size(), opacityMode, *msaaSampleCount));
+ OwnPtr<RecordingImageBufferFallbackSurfaceFactory> surfaceFactory = createSurfaceFactory(deviceSize, msaaSampleCount);
+
+ if (shouldUseDisplayList(deviceSize)) {
+ OwnPtr<ImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(deviceSize, surfaceFactory.release(), opacityMode));
if (surface->isValid())
return surface.release();
+ surfaceFactory = createSurfaceFactory(deviceSize, msaaSampleCount); // recreate because old previous one was released
}
- return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode));
+ return surfaceFactory->createSurface(deviceSize, opacityMode);
}
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);
OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
if (!surface->isValid())
return;
+
m_imageBuffer = ImageBuffer::create(surface.release());
+ m_imageBuffer->setClient(this);
m_didFailToCreateImageBuffer = false;
return;
}
+ m_imageBuffer->setClient(this);
m_imageBuffer->context()->setShouldClampToSourceRect(false);
+ 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
// See CanvasRenderingContext2D::State::State() for more information.
m_imageBuffer->context()->setMiterLimit(10);
m_imageBuffer->context()->setStrokeThickness(1);
+#if ENABLE(ASSERT)
+ 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)
+{
+#if ENABLE(OILPAN)
+ visitor->trace(m_observers);
+ visitor->trace(m_context);
+#endif
+ DocumentVisibilityObserver::trace(visitor);
+ HTMLElement::trace(visitor);
}
void HTMLCanvasElement::updateExternallyAllocatedMemory() const
GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
{
- if (m_didFailToCreateImageBuffer) {
- ASSERT(!hasImageBuffer());
- return 0;
- }
+ if (!hasImageBuffer())
+ return nullptr;
return drawingContext();
}
Image* HTMLCanvasElement::copiedImage() const
{
if (!m_copiedImage && buffer()) {
- if (m_context)
+ if (m_context && m_context->is3d()) {
+ toWebGLRenderingContext(m_context.get())->setSavingImage(true);
m_context->paintRenderingResultsToCanvas();
+ toWebGLRenderingContext(m_context.get())->setSavingImage(false);
+ }
m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
updateExternallyAllocatedMemory();
}
{
m_contextStateSaver.clear(); // uses context owned by m_imageBuffer
m_imageBuffer.clear();
+ resetDirtyRect();
updateExternallyAllocatedMemory();
}
+bool HTMLCanvasElement::hasValidImageBuffer() const
+{
+ return m_imageBuffer && m_imageBuffer->isSurfaceValid();
+}
+
void HTMLCanvasElement::clearCopiedImage()
{
- m_copiedImage.clear();
+ if (m_copiedImage) {
+ m_copiedImage.clear();
+ updateExternallyAllocatedMemory();
+ }
m_didClearImageBuffer = false;
- updateExternallyAllocatedMemory();
}
AffineTransform HTMLCanvasElement::baseTransform() const
void HTMLCanvasElement::didChangeVisibilityState(PageVisibilityState visibility)
{
- if (hasImageBuffer()) {
- bool hidden = visibility != PageVisibilityStateVisible;
- if (hidden) {
- clearCopiedImage();
- if (is3D()) {
- discardImageBuffer();
- }
- }
- if (hasImageBuffer()) {
- m_imageBuffer->setIsHidden(hidden);
+ if (!m_context)
+ return;
+ bool hidden = visibility != PageVisibilityStateVisible;
+ m_context->setIsHidden(hidden);
+ if (hidden) {
+ clearCopiedImage();
+ if (is3D()) {
+ discardImageBuffer();
}
}
}
return nullptr;
}
- if (mode == CopySourceImageIfVolatile) {
- *status = NormalSourceImageStatus;
- return copiedImage();
- }
-
if (m_context && m_context->is3d()) {
m_context->paintRenderingResultsToCanvas();
*status = ExternalSourceImageStatus;
- } else {
+
+ // can't create SkImage from WebGLImageBufferSurface (contains only SkBitmap)
+ return m_imageBuffer->copyImage(DontCopyBackingStore, Unscaled);
+ }
+
+ RefPtr<SkImage> image = m_imageBuffer->newImageSnapshot();
+ if (image) {
*status = NormalSourceImageStatus;
+
+ return StaticBitmapImage::create(image.release());
}
- return m_imageBuffer->copyImage(DontCopyBackingStore, Unscaled);
+
+
+ *status = InvalidSourceImageStatus;
+
+ return nullptr;
}
bool HTMLCanvasElement::wouldTaintOrigin(SecurityOrigin*) const