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