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