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