• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2004, 2006, 2007 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 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 
HTMLCanvasElement(const QualifiedName & tagName,Document * document)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 
create(Document * document)101 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
102 {
103     return adoptRef(new HTMLCanvasElement(canvasTag, document));
104 }
105 
create(const QualifiedName & tagName,Document * document)106 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
107 {
108     return adoptRef(new HTMLCanvasElement(tagName, document));
109 }
110 
~HTMLCanvasElement()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 
parseMappedAttribute(Attribute * attr)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 
createRenderer(RenderArena * arena,RenderStyle * style)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 
addObserver(CanvasObserver * observer)138 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
139 {
140     m_observers.add(observer);
141 }
142 
removeObserver(CanvasObserver * observer)143 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
144 {
145     m_observers.remove(observer);
146 }
147 
setHeight(int value)148 void HTMLCanvasElement::setHeight(int value)
149 {
150     setAttribute(heightAttr, String::number(value));
151 }
152 
setWidth(int value)153 void HTMLCanvasElement::setWidth(int value)
154 {
155     setAttribute(widthAttr, String::number(value));
156 }
157 
getContext(const String & type,CanvasContextAttributes * attrs)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 
didDraw(const FloatRect & rect)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 
reset()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 
paint(GraphicsContext * context,const IntRect & r)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)
is3D() const308 bool HTMLCanvasElement::is3D() const
309 {
310     return m_context && m_context->is3d();
311 }
312 #endif
313 
makeRenderingResultsAvailable()314 void HTMLCanvasElement::makeRenderingResultsAvailable()
315 {
316     if (m_context)
317         m_context->paintRenderingResultsToCanvas();
318 }
319 
makePresentationCopy()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 
clearPresentationCopy()328 void HTMLCanvasElement::clearPresentationCopy()
329 {
330     m_presentedImage.clear();
331 }
332 
setSurfaceSize(const IntSize & size)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 
toDataURL(const String & mimeType,const double * quality,ExceptionCode & ec)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 
getImageData()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 
convertLogicalToDevice(const FloatRect & logicalRect) const385 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 
convertLogicalToDevice(const FloatSize & logicalSize) const396 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 
convertToValidDeviceSize(float width,float height) const404 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 
securityOrigin() const420 const SecurityOrigin& HTMLCanvasElement::securityOrigin() const
421 {
422     return *document()->securityOrigin();
423 }
424 
styleSelector()425 CSSStyleSelector* HTMLCanvasElement::styleSelector()
426 {
427     return document()->styleSelector();
428 }
429 
createImageBuffer() const430 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 
drawingContext() const463 GraphicsContext* HTMLCanvasElement::drawingContext() const
464 {
465     return buffer() ? m_imageBuffer->context() : 0;
466 }
467 
buffer() const468 ImageBuffer* HTMLCanvasElement::buffer() const
469 {
470     if (!m_hasCreatedImageBuffer)
471         createImageBuffer();
472     return m_imageBuffer.get();
473 }
474 
copiedImage() const475 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 
clearCopiedImage()485 void HTMLCanvasElement::clearCopiedImage()
486 {
487     m_copiedImage.clear();
488 }
489 
baseTransform() const490 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