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 "HTMLCanvasElement.h"
     30 
     31 #include "Attribute.h"
     32 #include "CanvasContextAttributes.h"
     33 #include "CanvasGradient.h"
     34 #include "CanvasPattern.h"
     35 #include "CanvasRenderingContext2D.h"
     36 #include "CanvasStyle.h"
     37 #include "Chrome.h"
     38 #include "Document.h"
     39 #include "ExceptionCode.h"
     40 #include "Frame.h"
     41 #include "GraphicsContext.h"
     42 #include "HTMLNames.h"
     43 #include "ImageBuffer.h"
     44 #include "ImageData.h"
     45 #include "MIMETypeRegistry.h"
     46 #include "Page.h"
     47 #include "RenderHTMLCanvas.h"
     48 #include "Settings.h"
     49 #include <math.h>
     50 #include <stdio.h>
     51 
     52 #if USE(JSC)
     53 #include <runtime/JSLock.h>
     54 #endif
     55 
     56 #if ENABLE(WEBGL)
     57 #include "WebGLContextAttributes.h"
     58 #include "WebGLRenderingContext.h"
     59 #endif
     60 
     61 namespace WebCore {
     62 
     63 using namespace HTMLNames;
     64 
     65 // These values come from the WhatWG spec.
     66 static const int DefaultWidth = 300;
     67 static const int DefaultHeight = 150;
     68 
     69 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
     70 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
     71 // in exchange for a smaller maximum canvas size.
     72 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
     73 
     74 //In Skia, we will also limit width/height to 32767.
     75 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
     76 
     77 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
     78     : HTMLElement(tagName, document)
     79     , m_size(DefaultWidth, DefaultHeight)
     80     , m_rendererIsCanvas(false)
     81     , m_ignoreReset(false)
     82 #ifdef ANDROID
     83     /* In Android we capture the drawing into a displayList, and then
     84        replay that list at various scale factors (sometimes zoomed out, other
     85        times zoomed in for "normal" reading, yet other times at arbitrary
     86        zoom values based on the user's choice). In all of these cases, we do
     87        not re-record the displayList, hence it is usually harmful to perform
     88        any pre-rounding, since we just don't know the actual drawing resolution
     89        at record time.
     90     */
     91     , m_pageScaleFactor(1)
     92 #else
     93     , m_pageScaleFactor(document->frame() ? document->frame()->page()->chrome()->scaleFactor() : 1)
     94 #endif
     95     , m_originClean(true)
     96     , m_hasCreatedImageBuffer(false)
     97 {
     98     ASSERT(hasTagName(canvasTag));
     99 }
    100 
    101 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
    102 {
    103     return adoptRef(new HTMLCanvasElement(canvasTag, document));
    104 }
    105 
    106 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
    107 {
    108     return adoptRef(new HTMLCanvasElement(tagName, document));
    109 }
    110 
    111 HTMLCanvasElement::~HTMLCanvasElement()
    112 {
    113     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    114     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    115         (*it)->canvasDestroyed(this);
    116 }
    117 
    118 void HTMLCanvasElement::parseMappedAttribute(Attribute* attr)
    119 {
    120     const QualifiedName& attrName = attr->name();
    121     if (attrName == widthAttr || attrName == heightAttr)
    122         reset();
    123     HTMLElement::parseMappedAttribute(attr);
    124 }
    125 
    126 RenderObject* HTMLCanvasElement::createRenderer(RenderArena* arena, RenderStyle* style)
    127 {
    128     Frame* frame = document()->frame();
    129     if (frame && frame->script()->canExecuteScripts(NotAboutToExecuteScript)) {
    130         m_rendererIsCanvas = true;
    131         return new (arena) RenderHTMLCanvas(this);
    132     }
    133 
    134     m_rendererIsCanvas = false;
    135     return HTMLElement::createRenderer(arena, style);
    136 }
    137 
    138 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
    139 {
    140     m_observers.add(observer);
    141 }
    142 
    143 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
    144 {
    145     m_observers.remove(observer);
    146 }
    147 
    148 void HTMLCanvasElement::setHeight(int value)
    149 {
    150     setAttribute(heightAttr, String::number(value));
    151 }
    152 
    153 void HTMLCanvasElement::setWidth(int value)
    154 {
    155     setAttribute(widthAttr, String::number(value));
    156 }
    157 
    158 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
    159 {
    160     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
    161     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
    162     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
    163     // context with any other type string will destroy any existing context.
    164 
    165     // FIXME - The code depends on the context not going away once created, to prevent JS from
    166     // seeing a dangling pointer. So for now we will disallow the context from being changed
    167     // once it is created.
    168     if (type == "2d") {
    169         if (m_context && !m_context->is2d())
    170             return 0;
    171         if (!m_context) {
    172             bool usesDashbardCompatibilityMode = false;
    173 #if ENABLE(DASHBOARD_SUPPORT)
    174             if (Settings* settings = document()->settings())
    175                 usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
    176 #endif
    177             m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode));
    178 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
    179             if (m_context) {
    180                 // Need to make sure a RenderLayer and compositing layer get created for the Canvas
    181                 setNeedsStyleRecalc(SyntheticStyleChange);
    182             }
    183 #endif
    184         }
    185         return m_context.get();
    186     }
    187 #if ENABLE(WEBGL)
    188     Settings* settings = document()->settings();
    189     if (settings && settings->webGLEnabled()
    190 #if !PLATFORM(CHROMIUM) && !PLATFORM(GTK)
    191         && settings->acceleratedCompositingEnabled()
    192 #endif
    193         ) {
    194         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
    195         // Once ratified, we will also accept "webgl" as the context name.
    196         if ((type == "webkit-3d") ||
    197             (type == "experimental-webgl")) {
    198             if (m_context && !m_context->is3d())
    199                 return 0;
    200             if (!m_context) {
    201                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
    202                 if (m_context) {
    203                     // Need to make sure a RenderLayer and compositing layer get created for the Canvas
    204                     setNeedsStyleRecalc(SyntheticStyleChange);
    205                 }
    206             }
    207             return m_context.get();
    208         }
    209     }
    210 #else
    211     UNUSED_PARAM(attrs);
    212 #endif
    213     return 0;
    214 }
    215 
    216 void HTMLCanvasElement::didDraw(const FloatRect& rect)
    217 {
    218     m_copiedImage.clear(); // Clear our image snapshot if we have one.
    219 
    220     if (RenderBox* ro = renderBox()) {
    221         FloatRect destRect = ro->contentBoxRect();
    222         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
    223         r.intersect(destRect);
    224         if (r.isEmpty() || m_dirtyRect.contains(r))
    225             return;
    226 
    227         m_dirtyRect.unite(r);
    228         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
    229     }
    230 
    231     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    232     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    233         (*it)->canvasChanged(this, rect);
    234 }
    235 
    236 void HTMLCanvasElement::reset()
    237 {
    238     if (m_ignoreReset)
    239         return;
    240 
    241     bool ok;
    242     bool hadImageBuffer = hasCreatedImageBuffer();
    243     int w = getAttribute(widthAttr).toInt(&ok);
    244     if (!ok || w < 0)
    245         w = DefaultWidth;
    246     int h = getAttribute(heightAttr).toInt(&ok);
    247     if (!ok || h < 0)
    248         h = DefaultHeight;
    249 
    250     IntSize oldSize = size();
    251     setSurfaceSize(IntSize(w, h)); // The image buffer gets cleared here.
    252 
    253 #if ENABLE(WEBGL)
    254     if (m_context && m_context->is3d() && oldSize != size())
    255         static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
    256 #endif
    257 
    258     if (m_context && m_context->is2d())
    259         static_cast<CanvasRenderingContext2D*>(m_context.get())->reset();
    260 
    261     if (RenderObject* renderer = this->renderer()) {
    262         if (m_rendererIsCanvas) {
    263             if (oldSize != size())
    264                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
    265             if (hadImageBuffer)
    266                 renderer->repaint();
    267         }
    268     }
    269 
    270     HashSet<CanvasObserver*>::iterator end = m_observers.end();
    271     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
    272         (*it)->canvasResized(this);
    273 }
    274 
    275 void HTMLCanvasElement::paint(GraphicsContext* context, const IntRect& r)
    276 {
    277     // Clear the dirty rect
    278     m_dirtyRect = FloatRect();
    279 
    280     if (context->paintingDisabled())
    281         return;
    282 
    283     if (m_context) {
    284         if (!m_context->paintsIntoCanvasBuffer())
    285             return;
    286         m_context->paintRenderingResultsToCanvas();
    287     }
    288 
    289     if (hasCreatedImageBuffer()) {
    290         ImageBuffer* imageBuffer = buffer();
    291         if (imageBuffer) {
    292             if (m_presentedImage)
    293                 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, r);
    294             else if (imageBuffer->drawsUsingCopy())
    295                 context->drawImage(copiedImage(), ColorSpaceDeviceRGB, r);
    296             else
    297                 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, r);
    298         }
    299     }
    300 
    301 #if ENABLE(WEBGL)
    302     if (is3D())
    303         static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
    304 #endif
    305 }
    306 
    307 #if ENABLE(WEBGL)
    308 bool HTMLCanvasElement::is3D() const
    309 {
    310     return m_context && m_context->is3d();
    311 }
    312 #endif
    313 
    314 void HTMLCanvasElement::makeRenderingResultsAvailable()
    315 {
    316     if (m_context)
    317         m_context->paintRenderingResultsToCanvas();
    318 }
    319 
    320 void HTMLCanvasElement::makePresentationCopy()
    321 {
    322     if (!m_presentedImage) {
    323         // The buffer contains the last presented data, so save a copy of it.
    324         m_presentedImage = buffer()->copyImage();
    325     }
    326 }
    327 
    328 void HTMLCanvasElement::clearPresentationCopy()
    329 {
    330     m_presentedImage.clear();
    331 }
    332 
    333 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
    334 {
    335     m_size = size;
    336     m_hasCreatedImageBuffer = false;
    337     m_imageBuffer.clear();
    338     m_copiedImage.clear();
    339 }
    340 
    341 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
    342 {
    343     if (!m_originClean) {
    344         ec = SECURITY_ERR;
    345         return String();
    346     }
    347 
    348     if (m_size.isEmpty() || !buffer())
    349         return String("data:,");
    350 
    351     String lowercaseMimeType = mimeType.lower();
    352 
    353     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
    354     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
    355         lowercaseMimeType = "image/png";
    356 
    357 #if USE(CG) || (USE(SKIA) && !PLATFORM(ANDROID))
    358     // FIXME: Consider using this code path on Android. http://b/4572024
    359     // Try to get ImageData first, as that may avoid lossy conversions.
    360     RefPtr<ImageData> imageData = getImageData();
    361 
    362     if (imageData)
    363         return ImageDataToDataURL(*imageData, lowercaseMimeType, quality);
    364 #endif
    365 
    366     makeRenderingResultsAvailable();
    367 
    368     return buffer()->toDataURL(lowercaseMimeType, quality);
    369 }
    370 
    371 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
    372 {
    373     if (!m_context || !m_context->is3d())
    374        return 0;
    375 
    376 #if ENABLE(WEBGL)
    377     WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
    378 
    379     return ctx->paintRenderingResultsToImageData();
    380 #else
    381     return 0;
    382 #endif
    383 }
    384 
    385 IntRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
    386 {
    387     // Prevent under/overflow by ensuring the rect's bounds stay within integer-expressible range
    388     int left = clampToInteger(floorf(logicalRect.x() * m_pageScaleFactor));
    389     int top = clampToInteger(floorf(logicalRect.y() * m_pageScaleFactor));
    390     int right = clampToInteger(ceilf(logicalRect.maxX() * m_pageScaleFactor));
    391     int bottom = clampToInteger(ceilf(logicalRect.maxY() * m_pageScaleFactor));
    392 
    393     return IntRect(IntPoint(left, top), convertToValidDeviceSize(right - left, bottom - top));
    394 }
    395 
    396 IntSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
    397 {
    398     // Prevent overflow by ensuring the rect's bounds stay within integer-expressible range
    399     float width = clampToInteger(ceilf(logicalSize.width() * m_pageScaleFactor));
    400     float height = clampToInteger(ceilf(logicalSize.height() * m_pageScaleFactor));
    401     return convertToValidDeviceSize(width, height);
    402 }
    403 
    404 IntSize HTMLCanvasElement::convertToValidDeviceSize(float width, float height) const
    405 {
    406     width = ceilf(width);
    407     height = ceilf(height);
    408 
    409     if (width < 1 || height < 1 || width * height > MaxCanvasArea)
    410         return IntSize();
    411 
    412 #if USE(SKIA)
    413     if (width > MaxSkiaDim || height > MaxSkiaDim)
    414         return IntSize();
    415 #endif
    416 
    417     return IntSize(width, height);
    418 }
    419 
    420 const SecurityOrigin& HTMLCanvasElement::securityOrigin() const
    421 {
    422     return *document()->securityOrigin();
    423 }
    424 
    425 CSSStyleSelector* HTMLCanvasElement::styleSelector()
    426 {
    427     return document()->styleSelector();
    428 }
    429 
    430 void HTMLCanvasElement::createImageBuffer() const
    431 {
    432     ASSERT(!m_imageBuffer);
    433 
    434     m_hasCreatedImageBuffer = true;
    435 
    436     FloatSize unscaledSize(width(), height());
    437     IntSize size = convertLogicalToDevice(unscaledSize);
    438     if (!size.width() || !size.height())
    439         return;
    440 
    441 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
    442     if (document()->settings()->canvasUsesAcceleratedDrawing())
    443         m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Accelerated);
    444     else
    445         m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Unaccelerated);
    446 #else
    447     m_imageBuffer = ImageBuffer::create(size);
    448 #endif
    449     // The convertLogicalToDevice MaxCanvasArea check should prevent common cases
    450     // where ImageBuffer::create() returns 0, however we could still be low on memory.
    451     if (!m_imageBuffer)
    452         return;
    453     m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()));
    454     m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
    455     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
    456 
    457 #if USE(JSC)
    458     JSC::JSLock lock(JSC::SilenceAssertionsOnly);
    459     scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(m_imageBuffer->dataSize());
    460 #endif
    461 }
    462 
    463 GraphicsContext* HTMLCanvasElement::drawingContext() const
    464 {
    465     return buffer() ? m_imageBuffer->context() : 0;
    466 }
    467 
    468 ImageBuffer* HTMLCanvasElement::buffer() const
    469 {
    470     if (!m_hasCreatedImageBuffer)
    471         createImageBuffer();
    472     return m_imageBuffer.get();
    473 }
    474 
    475 Image* HTMLCanvasElement::copiedImage() const
    476 {
    477     if (!m_copiedImage && buffer()) {
    478         if (m_context)
    479             m_context->paintRenderingResultsToCanvas();
    480         m_copiedImage = buffer()->copyImage();
    481     }
    482     return m_copiedImage.get();
    483 }
    484 
    485 void HTMLCanvasElement::clearCopiedImage()
    486 {
    487     m_copiedImage.clear();
    488 }
    489 
    490 AffineTransform HTMLCanvasElement::baseTransform() const
    491 {
    492     ASSERT(m_hasCreatedImageBuffer);
    493     FloatSize unscaledSize(width(), height());
    494     IntSize size = convertLogicalToDevice(unscaledSize);
    495     AffineTransform transform;
    496     if (size.width() && size.height())
    497         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
    498     return m_imageBuffer->baseTransform() * transform;
    499 }
    500 
    501 }
    502