Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
      3  * Copyright (C) 2007 Alp Toker <alp (at) 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 COMPUTER, 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 COMPUTER, 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 "core/html/HTMLCanvasElement.h"
     30 
     31 #include <math.h>
     32 #include "HTMLNames.h"
     33 #include "RuntimeEnabledFeatures.h"
     34 #include "bindings/v8/ExceptionMessages.h"
     35 #include "bindings/v8/ExceptionState.h"
     36 #include "bindings/v8/ScriptController.h"
     37 #include "core/dom/Document.h"
     38 #include "core/dom/ExceptionCode.h"
     39 #include "core/html/ImageData.h"
     40 #include "core/html/canvas/Canvas2DContextAttributes.h"
     41 #include "core/html/canvas/CanvasRenderingContext2D.h"
     42 #include "core/html/canvas/WebGLContextAttributes.h"
     43 #include "core/html/canvas/WebGLRenderingContext.h"
     44 #include "core/frame/Frame.h"
     45 #include "core/frame/Settings.h"
     46 #include "core/rendering/RenderHTMLCanvas.h"
     47 #include "platform/MIMETypeRegistry.h"
     48 #include "platform/graphics/Canvas2DImageBufferSurface.h"
     49 #include "platform/graphics/GraphicsContextStateSaver.h"
     50 #include "platform/graphics/ImageBuffer.h"
     51 #include "platform/graphics/UnacceleratedImageBufferSurface.h"
     52 #include "platform/graphics/gpu/WebGLImageBufferSurface.h"
     53 #include "public/platform/Platform.h"
     54 
     55 namespace WebCore {
     56 
     57 using namespace HTMLNames;
     58 
     59 // These values come from the WhatWG spec.
     60 static const int DefaultWidth = 300;
     61 static const int DefaultHeight = 150;
     62 
     63 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
     64 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
     65 // in exchange for a smaller maximum canvas size.
     66 static const int MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
     67 
     68 //In Skia, we will also limit width/height to 32767.
     69 static const int MaxSkiaDim = 32767; // Maximum width/height in CSS pixels.
     70 
     71 HTMLCanvasElement::HTMLCanvasElement(Document& document)
     72     : HTMLElement(canvasTag, document)
     73     , m_size(DefaultWidth, DefaultHeight)
     74     , m_rendererIsCanvas(false)
     75     , m_ignoreReset(false)
     76     , m_accelerationDisabled(false)
     77     , m_externallyAllocatedMemory(0)
     78     , m_originClean(true)
     79     , m_didFailToCreateImageBuffer(false)
     80     , m_didClearImageBuffer(false)
     81 {
     82     ScriptWrappable::init(this);
     83 }
     84 
     85 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
     86 {
     87     return adoptRef(new HTMLCanvasElement(document));
     88 }
     89 
     90 HTMLCanvasElement::~HTMLCanvasElement()
     91 {
     92     setExternallyAllocatedMemory(0);
     93     HashSet<CanvasObserver*>::iterator end = m_observers.end();
     94     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
     95         (*it)->canvasDestroyed(this);
     96 
     97     m_context.clear(); // Ensure this goes away before the ImageBuffer.
     98 }
     99 
    100 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    101 {
    102     if (name == widthAttr || name == heightAttr)
    103         reset();
    104     HTMLElement::parseAttribute(name, value);
    105 }
    106 
    107 RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style)
    108 {
    109     Frame* frame = document().frame();
    110     if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) {
    111         m_rendererIsCanvas = true;
    112         return new RenderHTMLCanvas(this);
    113     }
    114 
    115     m_rendererIsCanvas = false;
    116     return HTMLElement::createRenderer(style);
    117 }
    118 
    119 Node::InsertionNotificationRequest HTMLCanvasElement::insertedInto(ContainerNode* node)
    120 {
    121     setIsInCanvasSubtree(true);
    122     return HTMLElement::insertedInto(node);
    123 }
    124 
    125 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
    126 {
    127     m_observers.add(observer);
    128 }
    129 
    130 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
    131 {
    132     m_observers.remove(observer);
    133 }
    134 
    135 void HTMLCanvasElement::setHeight(int value)
    136 {
    137     setIntegralAttribute(heightAttr, value);
    138 }
    139 
    140 void HTMLCanvasElement::setWidth(int value)
    141 {
    142     setIntegralAttribute(widthAttr, value);
    143 }
    144 
    145 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
    146 {
    147     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
    148     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
    149     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
    150     // context with any other type string will destroy any existing context.
    151     enum ContextType {
    152         Context2d,
    153         ContextWebkit3d,
    154         ContextExperimentalWebgl,
    155         ContextWebgl,
    156         // Only add new items to the end and keep the order of existing items.
    157         ContextTypeCount,
    158     };
    159 
    160     // FIXME - The code depends on the context not going away once created, to prevent JS from
    161     // seeing a dangling pointer. So for now we will disallow the context from being changed
    162     // once it is created.
    163     if (type == "2d") {
    164         if (m_context && !m_context->is2d())
    165             return 0;
    166         if (!m_context) {
    167             blink::Platform::current()->histogramEnumeration("Canvas.ContextType", Context2d, ContextTypeCount);
    168             m_context = CanvasRenderingContext2D::create(this, static_cast<Canvas2DContextAttributes*>(attrs), document().inQuirksMode());
    169             if (m_context)
    170                 scheduleLayerUpdate();
    171         }
    172         return m_context.get();
    173     }
    174 
    175     Settings* settings = document().settings();
    176     if (settings && settings->webGLEnabled()) {
    177         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
    178         // Now that WebGL is ratified, we will also accept "webgl" as the context name in Chrome.
    179         ContextType contextType;
    180         bool is3dContext = true;
    181         if (type == "webkit-3d")
    182             contextType = ContextWebkit3d;
    183         else if (type == "experimental-webgl")
    184             contextType = ContextExperimentalWebgl;
    185         else if (type == "webgl")
    186             contextType = ContextWebgl;
    187         else
    188             is3dContext = false;
    189 
    190         if (is3dContext) {
    191             if (m_context && !m_context->is3d())
    192                 return 0;
    193             if (!m_context) {
    194                 blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
    195                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
    196                 if (m_context)
    197                     scheduleLayerUpdate();
    198             }
    199             return m_context.get();
    200         }
    201     }
    202     return 0;
    203 }
    204 
    205 void HTMLCanvasElement::didDraw(const FloatRect& rect)
    206 {
    207     clearCopiedImage();
    208 
    209     if (RenderBox* ro = renderBox()) {
    210         FloatRect destRect = ro->contentBoxRect();
    211         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
    212         r.intersect(destRect);
    213         if (r.isEmpty() || m_dirtyRect.contains(r))
    214             return;
    215 
    216         m_dirtyRect.unite(r);
    217         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
    218     }
    219 
    220     notifyObserversCanvasChanged(rect);
    221 }
    222 
    223 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
    224 {
    225     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    226     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    227         (*it)->canvasChanged(this, rect);
    228 }
    229 
    230 void HTMLCanvasElement::reset()
    231 {
    232     if (m_ignoreReset)
    233         return;
    234 
    235     bool ok;
    236     bool hadImageBuffer = hasImageBuffer();
    237 
    238     int w = getAttribute(widthAttr).toInt(&ok);
    239     if (!ok || w < 0)
    240         w = DefaultWidth;
    241 
    242     int h = getAttribute(heightAttr).toInt(&ok);
    243     if (!ok || h < 0)
    244         h = DefaultHeight;
    245 
    246     if (m_contextStateSaver) {
    247         // Reset to the initial graphics context state.
    248         m_contextStateSaver->restore();
    249         m_contextStateSaver->save();
    250     }
    251 
    252     if (m_context && m_context->is2d())
    253         toCanvasRenderingContext2D(m_context.get())->reset();
    254 
    255     IntSize oldSize = size();
    256     IntSize newSize(w, h);
    257 
    258     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
    259     // This optimization is only done for 2D canvases for now.
    260     if (hadImageBuffer && oldSize == newSize && m_context && m_context->is2d()) {
    261         if (!m_didClearImageBuffer)
    262             clearImageBuffer();
    263         return;
    264     }
    265 
    266     setSurfaceSize(newSize);
    267 
    268     if (m_context && m_context->is3d() && oldSize != size())
    269         toWebGLRenderingContext(m_context.get())->reshape(width(), height());
    270 
    271     if (RenderObject* renderer = this->renderer()) {
    272         if (m_rendererIsCanvas) {
    273             if (oldSize != size()) {
    274                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
    275                 if (renderBox() && renderBox()->hasAcceleratedCompositing())
    276                     renderBox()->contentChanged(CanvasChanged);
    277             }
    278             if (hadImageBuffer)
    279                 renderer->repaint();
    280         }
    281     }
    282 
    283     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    284     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    285         (*it)->canvasResized(this);
    286 }
    287 
    288 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
    289 {
    290     ASSERT(m_context);
    291 
    292     if (!m_context->isAccelerated())
    293         return true;
    294 
    295     if (renderBox() && renderBox()->hasAcceleratedCompositing())
    296         return false;
    297 
    298     return true;
    299 }
    300 
    301 
    302 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
    303 {
    304     // Clear the dirty rect
    305     m_dirtyRect = FloatRect();
    306 
    307     if (context->paintingDisabled())
    308         return;
    309 
    310     if (m_context) {
    311         if (!paintsIntoCanvasBuffer() && !document().printing())
    312             return;
    313         m_context->paintRenderingResultsToCanvas();
    314     }
    315 
    316     if (hasImageBuffer()) {
    317         ImageBuffer* imageBuffer = buffer();
    318         if (imageBuffer) {
    319             CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy;
    320             if (m_presentedImage)
    321                 context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation, useLowQualityScale);
    322             else
    323                 context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), compositeOperator, blink::WebBlendModeNormal, useLowQualityScale);
    324         }
    325     }
    326 
    327     if (is3D())
    328         toWebGLRenderingContext(m_context.get())->markLayerComposited();
    329 }
    330 
    331 bool HTMLCanvasElement::is3D() const
    332 {
    333     return m_context && m_context->is3d();
    334 }
    335 
    336 void HTMLCanvasElement::makePresentationCopy()
    337 {
    338     if (!m_presentedImage) {
    339         // The buffer contains the last presented data, so save a copy of it.
    340         m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
    341     }
    342 }
    343 
    344 void HTMLCanvasElement::clearPresentationCopy()
    345 {
    346     m_presentedImage.clear();
    347 }
    348 
    349 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
    350 {
    351     m_size = size;
    352     m_didFailToCreateImageBuffer = false;
    353     m_contextStateSaver.clear();
    354     m_imageBuffer.clear();
    355     setExternallyAllocatedMemory(0);
    356     clearCopiedImage();
    357 }
    358 
    359 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
    360 {
    361     String lowercaseMimeType = mimeType.lower();
    362 
    363     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
    364     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
    365         lowercaseMimeType = "image/png";
    366 
    367     return lowercaseMimeType;
    368 }
    369 
    370 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState)
    371 {
    372     if (!m_originClean) {
    373         exceptionState.throwSecurityError("Tainted canvases may not be exported.");
    374         return String();
    375     }
    376 
    377     if (m_size.isEmpty() || !buffer())
    378         return String("data:,");
    379 
    380     String encodingMimeType = toEncodingMimeType(mimeType);
    381 
    382     // Try to get ImageData first, as that may avoid lossy conversions.
    383     RefPtr<ImageData> imageData = getImageData();
    384 
    385     if (imageData)
    386         return ImageDataToDataURL(ImageDataBuffer(imageData->size(), imageData->data()), encodingMimeType, quality);
    387 
    388     if (m_context)
    389         m_context->paintRenderingResultsToCanvas();
    390 
    391     return buffer()->toDataURL(encodingMimeType, quality);
    392 }
    393 
    394 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
    395 {
    396     if (!m_context || !m_context->is3d())
    397        return 0;
    398     return toWebGLRenderingContext(m_context.get())->paintRenderingResultsToImageData();
    399 }
    400 
    401 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
    402 {
    403     return document().securityOrigin();
    404 }
    405 
    406 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
    407 {
    408     if (m_context && !m_context->is2d())
    409         return false;
    410 
    411     if (m_accelerationDisabled)
    412         return false;
    413 
    414     Settings* settings = document().settings();
    415     if (!settings || !settings->accelerated2dCanvasEnabled())
    416         return false;
    417 
    418     // Do not use acceleration for small canvas.
    419     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
    420         return false;
    421 
    422     if (!blink::Platform::current()->canAccelerate2dCanvas())
    423         return false;
    424 
    425     return true;
    426 }
    427 
    428 PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount)
    429 {
    430     OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
    431 
    432     *msaaSampleCount = 0;
    433     if (is3D())
    434         return adoptPtr(new WebGLImageBufferSurface(size(), opacityMode));
    435 
    436     if (shouldAccelerate(deviceSize)) {
    437         if (document().settings())
    438             *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount();
    439         OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size(), opacityMode, *msaaSampleCount));
    440         if (surface->isValid())
    441             return surface.release();
    442     }
    443 
    444     return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode));
    445 }
    446 
    447 void HTMLCanvasElement::createImageBuffer()
    448 {
    449     ASSERT(!m_imageBuffer);
    450 
    451     m_didFailToCreateImageBuffer = true;
    452     m_didClearImageBuffer = true;
    453 
    454     IntSize deviceSize = size();
    455     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
    456         return;
    457 
    458     if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
    459         return;
    460 
    461     if (!deviceSize.width() || !deviceSize.height())
    462         return;
    463 
    464     int msaaSampleCount;
    465     OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
    466     if (!surface->isValid())
    467         return;
    468     m_imageBuffer = ImageBuffer::create(surface.release());
    469 
    470     m_didFailToCreateImageBuffer = false;
    471 
    472     setExternallyAllocatedMemory(4 * width() * height());
    473 
    474     if (is3D()) {
    475         // Early out for WebGL canvases
    476         m_contextStateSaver.clear();
    477         return;
    478     }
    479 
    480     m_imageBuffer->context()->setShouldClampToSourceRect(false);
    481     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
    482     // Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the
    483     // rendering mode is accelerated or not. For consistency, we don't want to apply AA in accelerated
    484     // canvases but not in unaccelerated canvases.
    485     if (!msaaSampleCount && document().settings() && !document().settings()->antialiased2dCanvasEnabled())
    486         m_imageBuffer->context()->setShouldAntialias(false);
    487     // GraphicsContext's defaults don't always agree with the 2d canvas spec.
    488     // See CanvasRenderingContext2D::State::State() for more information.
    489     m_imageBuffer->context()->setMiterLimit(10);
    490     m_imageBuffer->context()->setStrokeThickness(1);
    491     m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
    492 
    493     // Recalculate compositing requirements if acceleration state changed.
    494     if (m_context)
    495         scheduleLayerUpdate();
    496 }
    497 
    498 void HTMLCanvasElement::setExternallyAllocatedMemory(intptr_t externallyAllocatedMemory)
    499 {
    500     v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory);
    501     m_externallyAllocatedMemory = externallyAllocatedMemory;
    502 }
    503 
    504 GraphicsContext* HTMLCanvasElement::drawingContext() const
    505 {
    506     return buffer() ? m_imageBuffer->context() : 0;
    507 }
    508 
    509 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
    510 {
    511     if (m_didFailToCreateImageBuffer) {
    512         ASSERT(!hasImageBuffer());
    513         return 0;
    514     }
    515 
    516     return drawingContext();
    517 }
    518 
    519 ImageBuffer* HTMLCanvasElement::buffer() const
    520 {
    521     if (!hasImageBuffer() && !m_didFailToCreateImageBuffer)
    522         const_cast<HTMLCanvasElement*>(this)->createImageBuffer();
    523     return m_imageBuffer.get();
    524 }
    525 
    526 void HTMLCanvasElement::ensureUnacceleratedImageBuffer()
    527 {
    528     if ((hasImageBuffer() && !m_imageBuffer->isAccelerated()) || m_didFailToCreateImageBuffer)
    529         return;
    530     m_imageBuffer.clear();
    531     OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
    532     m_imageBuffer = ImageBuffer::create(size(), opacityMode);
    533     m_didFailToCreateImageBuffer = !m_imageBuffer;
    534 }
    535 
    536 Image* HTMLCanvasElement::copiedImage() const
    537 {
    538     if (!m_copiedImage && buffer()) {
    539         if (m_context)
    540             m_context->paintRenderingResultsToCanvas();
    541         m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
    542     }
    543     return m_copiedImage.get();
    544 }
    545 
    546 void HTMLCanvasElement::clearImageBuffer()
    547 {
    548     ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
    549     ASSERT(!m_didClearImageBuffer);
    550     ASSERT(m_context);
    551 
    552     m_didClearImageBuffer = true;
    553 
    554     if (m_context->is2d()) {
    555         // No need to undo transforms/clip/etc. because we are called right
    556         // after the context is reset.
    557         toCanvasRenderingContext2D(m_context.get())->clearRect(0, 0, width(), height());
    558     }
    559 }
    560 
    561 void HTMLCanvasElement::clearCopiedImage()
    562 {
    563     m_copiedImage.clear();
    564     m_didClearImageBuffer = false;
    565 }
    566 
    567 AffineTransform HTMLCanvasElement::baseTransform() const
    568 {
    569     ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
    570     return m_imageBuffer->baseTransform();
    571 }
    572 
    573 }
    574