Unreviewed, rolling out r121058.
[WebKit-https.git] / Source / WebCore / html / HTMLCanvasElement.cpp
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 #if PLATFORM(CHROMIUM)
62 #include <public/Platform.h>
63 #endif
64
65 namespace WebCore {
66
67 using namespace HTMLNames;
68
69 // These values come from the WhatWG spec.
70 static const int DefaultWidth = 300;
71 static const int DefaultHeight = 150;
72
73 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
74 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
75 // in exchange for a smaller maximum canvas size.
76 static const float MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
77
78 //In Skia, we will also limit width/height to 32767.
79 static const float MaxSkiaDim = 32767.0F; // Maximum width/height in CSS pixels.
80
81 HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document* document)
82     : HTMLElement(tagName, document)
83     , m_size(DefaultWidth, DefaultHeight)
84     , m_rendererIsCanvas(false)
85     , m_ignoreReset(false)
86 #if ENABLE(HIGH_DPI_CANVAS)
87       // NOTE: High-DPI canvas requires the platform-specific ImageBuffer implementation to respect
88       // the resolutionScale parameter.
89     , m_deviceScaleFactor(document->frame() ? document->frame()->page()->deviceScaleFactor() : 1)
90 #else
91     , m_deviceScaleFactor(1)
92 #endif
93     , m_originClean(true)
94     , m_hasCreatedImageBuffer(false)
95     , m_didClearImageBuffer(false)
96 {
97     ASSERT(hasTagName(canvasTag));
98 }
99
100 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document* document)
101 {
102     return adoptRef(new HTMLCanvasElement(canvasTag, document));
103 }
104
105 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(const QualifiedName& tagName, Document* document)
106 {
107     return adoptRef(new HTMLCanvasElement(tagName, document));
108 }
109
110 HTMLCanvasElement::~HTMLCanvasElement()
111 {
112     HashSet<CanvasObserver*>::iterator end = m_observers.end();
113     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
114         (*it)->canvasDestroyed(this);
115
116     m_context.clear(); // Ensure this goes away before the ImageBuffer.
117 }
118
119 void HTMLCanvasElement::parseAttribute(const Attribute& attribute)
120 {
121     if (attribute.name() == widthAttr || attribute.name() == heightAttr)
122         reset();
123     HTMLElement::parseAttribute(attribute);
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 = CanvasRenderingContext2D::create(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) && !PLATFORM(EFL)
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     clearCopiedImage();
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     if (m_contextStateSaver) {
251         // Reset to the initial graphics context state.
252         m_contextStateSaver->restore();
253         m_contextStateSaver->save();
254     }
255
256     if (m_context && m_context->is2d()) {
257         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
258         context2D->reset();
259     }
260
261     IntSize oldSize = size();
262     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
263     // This optimization is only done for 2D canvases for now.
264     if (m_hasCreatedImageBuffer && oldSize == IntSize(w, h) && m_context && m_context->is2d()) {
265         if (!m_didClearImageBuffer)
266             clearImageBuffer();
267         return;
268     }
269     setSurfaceSize(IntSize(w, h));
270
271 #if ENABLE(WEBGL)
272     if (m_context && m_context->is3d() && oldSize != size())
273         static_cast<WebGLRenderingContext*>(m_context.get())->reshape(width(), height());
274 #endif
275
276     if (RenderObject* renderer = this->renderer()) {
277         if (m_rendererIsCanvas) {
278             if (oldSize != size()) {
279                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
280 #if USE(ACCELERATED_COMPOSITING)
281                 if (renderBox() && renderBox()->hasAcceleratedCompositing())
282                     renderBox()->contentChanged(CanvasChanged);
283 #endif
284             }
285             if (hadImageBuffer)
286                 renderer->repaint();
287         }
288     }
289
290     HashSet<CanvasObserver*>::iterator end = m_observers.end();
291     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
292         (*it)->canvasResized(this);
293 }
294
295 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
296 {
297     ASSERT(m_context);
298 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
299     if (m_context->is2d())
300         return true;
301 #endif
302
303 #if USE(ACCELERATED_COMPOSITING)
304     if (!m_context->isAccelerated())
305         return true;
306
307     if (renderBox() && renderBox()->hasAcceleratedCompositing())
308         return false;
309 #endif
310     return true;
311 }
312
313
314 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
315 {
316     // Clear the dirty rect
317     m_dirtyRect = FloatRect();
318
319     if (context->paintingDisabled())
320         return;
321     
322     if (m_context) {
323         if (!paintsIntoCanvasBuffer() && !document()->printing())
324             return;
325         m_context->paintRenderingResultsToCanvas();
326     }
327
328     if (hasCreatedImageBuffer()) {
329         ImageBuffer* imageBuffer = buffer();
330         if (imageBuffer) {
331             if (m_presentedImage)
332                 context->drawImage(m_presentedImage.get(), ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, DoNotRespectImageOrientation, useLowQualityScale);
333             else
334                 context->drawImageBuffer(imageBuffer, ColorSpaceDeviceRGB, pixelSnappedIntRect(r), CompositeSourceOver, useLowQualityScale);
335         }
336     }
337
338 #if ENABLE(WEBGL)    
339     if (is3D())
340         static_cast<WebGLRenderingContext*>(m_context.get())->markLayerComposited();
341 #endif
342 }
343
344 #if ENABLE(WEBGL)
345 bool HTMLCanvasElement::is3D() const
346 {
347     return m_context && m_context->is3d();
348 }
349 #endif
350
351 void HTMLCanvasElement::makeRenderingResultsAvailable()
352 {
353     if (m_context)
354         m_context->paintRenderingResultsToCanvas();
355 }
356
357 void HTMLCanvasElement::makePresentationCopy()
358 {
359     if (!m_presentedImage) {
360         // The buffer contains the last presented data, so save a copy of it.
361         m_presentedImage = buffer()->copyImage(CopyBackingStore);
362     }
363 }
364
365 void HTMLCanvasElement::clearPresentationCopy()
366 {
367     m_presentedImage.clear();
368 }
369
370 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
371 {
372     m_size = size;
373     m_hasCreatedImageBuffer = false;
374     m_contextStateSaver.clear();
375     m_imageBuffer.clear();
376     clearCopiedImage();
377 }
378
379 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
380 {
381     String lowercaseMimeType = mimeType.lower();
382
383     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
384     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
385         lowercaseMimeType = "image/png";
386
387     return lowercaseMimeType;
388 }
389
390 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionCode& ec)
391 {
392     if (!m_originClean) {
393         ec = SECURITY_ERR;
394         return String();
395     }
396
397     if (m_size.isEmpty() || !buffer())
398         return String("data:,");
399
400     String encodingMimeType = toEncodingMimeType(mimeType);
401
402 #if USE(CG) || USE(SKIA)
403     // Try to get ImageData first, as that may avoid lossy conversions.
404     RefPtr<ImageData> imageData = getImageData();
405
406     if (imageData)
407         return ImageDataToDataURL(*imageData, encodingMimeType, quality);
408 #endif
409
410     makeRenderingResultsAvailable();
411
412     return buffer()->toDataURL(encodingMimeType, quality);
413 }
414
415 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
416 {
417     if (!m_context || !m_context->is3d())
418        return 0;
419
420 #if ENABLE(WEBGL)    
421     WebGLRenderingContext* ctx = static_cast<WebGLRenderingContext*>(m_context.get());
422
423     return ctx->paintRenderingResultsToImageData();
424 #else
425     return 0;
426 #endif
427 }
428
429 FloatRect HTMLCanvasElement::convertLogicalToDevice(const FloatRect& logicalRect) const
430 {
431     FloatRect deviceRect(logicalRect);
432     deviceRect.scale(m_deviceScaleFactor);
433
434     float x = floorf(deviceRect.x());
435     float y = floorf(deviceRect.y());
436     float w = ceilf(deviceRect.maxX() - x);
437     float h = ceilf(deviceRect.maxY() - y);
438     deviceRect.setX(x);
439     deviceRect.setY(y);
440     deviceRect.setWidth(w);
441     deviceRect.setHeight(h);
442
443     return deviceRect;
444 }
445
446 FloatSize HTMLCanvasElement::convertLogicalToDevice(const FloatSize& logicalSize) const
447 {
448     float width = ceilf(logicalSize.width() * m_deviceScaleFactor);
449     float height = ceilf(logicalSize.height() * m_deviceScaleFactor);
450     return FloatSize(width, height);
451 }
452
453 FloatSize HTMLCanvasElement::convertDeviceToLogical(const FloatSize& deviceSize) const
454 {
455     float width = ceilf(deviceSize.width() / m_deviceScaleFactor);
456     float height = ceilf(deviceSize.height() / m_deviceScaleFactor);
457     return FloatSize(width, height);
458 }
459
460 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
461 {
462     return document()->securityOrigin();
463 }
464
465 StyleResolver* HTMLCanvasElement::styleResolver()
466 {
467     return document()->styleResolver();
468 }
469
470 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
471 {
472 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
473     UNUSED_PARAM(size);
474     return document()->settings() && document()->settings()->canvasUsesAcceleratedDrawing();
475 #elif ENABLE(ACCELERATED_2D_CANVAS)
476     if (m_context && !m_context->is2d())
477         return false;
478
479     Settings* settings = document()->settings();
480     if (!settings || !settings->accelerated2dCanvasEnabled())
481         return false;
482
483     // Do not use acceleration for small canvas.
484     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
485         return false;
486
487 #if PLATFORM(CHROMIUM)
488     if (!WebKit::Platform::current()->canAccelerate2dCanvas())
489         return false;
490 #endif
491
492     return true;
493 #else
494     UNUSED_PARAM(size);
495     return false;
496 #endif
497 }
498
499 bool HTMLCanvasElement::shouldDefer() const
500 {
501 #if USE(SKIA)
502     if (m_context && !m_context->is2d())
503         return false;
504
505     Settings* settings = document()->settings();
506     if (!settings || !settings->deferred2dCanvasEnabled())
507         return false;
508
509     return true;
510 #else
511     return false;
512 #endif
513 }
514
515 void HTMLCanvasElement::createImageBuffer() const
516 {
517     ASSERT(!m_imageBuffer);
518
519     m_hasCreatedImageBuffer = true;
520     m_didClearImageBuffer = true;
521
522     FloatSize logicalSize = size();
523     FloatSize deviceSize = convertLogicalToDevice(logicalSize);
524     if (!deviceSize.isExpressibleAsIntSize())
525         return;
526
527     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
528         return;
529 #if USE(SKIA)
530     if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
531         return;
532 #endif
533
534     IntSize bufferSize(deviceSize.width(), deviceSize.height());
535     if (!bufferSize.width() || !bufferSize.height())
536         return;
537
538     RenderingMode renderingMode = shouldAccelerate(bufferSize) ? Accelerated : 
539 #if USE(SKIA)
540         UnacceleratedNonPlatformBuffer;
541 #else
542         Unaccelerated;
543 #endif
544     DeferralMode deferralMode = shouldDefer() ? Deferred : NonDeferred;
545     m_imageBuffer = ImageBuffer::create(size(), m_deviceScaleFactor, ColorSpaceDeviceRGB, renderingMode, deferralMode);
546     if (!m_imageBuffer)
547         return;
548     m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
549     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
550     m_imageBuffer->context()->setStrokeThickness(1);
551     m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
552
553 #if USE(JSC)
554     JSC::JSLock lock(JSC::SilenceAssertionsOnly);
555     size_t numBytes = 4 * m_imageBuffer->internalSize().width() * m_imageBuffer->internalSize().height();
556     scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(numBytes);
557 #endif
558
559 #if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
560     if (m_context && m_context->is2d())
561         // Recalculate compositing requirements if acceleration state changed.
562         const_cast<HTMLCanvasElement*>(this)->setNeedsStyleRecalc(SyntheticStyleChange);
563 #endif
564 }
565
566 GraphicsContext* HTMLCanvasElement::drawingContext() const
567 {
568     return buffer() ? m_imageBuffer->context() : 0;
569 }
570
571 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
572 {
573     if (!m_hasCreatedImageBuffer)
574         return 0;
575
576     return drawingContext();
577 }
578
579 ImageBuffer* HTMLCanvasElement::buffer() const
580 {
581     if (!m_hasCreatedImageBuffer)
582         createImageBuffer();
583     return m_imageBuffer.get();
584 }
585
586 Image* HTMLCanvasElement::copiedImage() const
587 {
588     if (!m_copiedImage && buffer()) {
589         if (m_context)
590             m_context->paintRenderingResultsToCanvas();
591         m_copiedImage = buffer()->copyImage(CopyBackingStore);
592     }
593     return m_copiedImage.get();
594 }
595
596 void HTMLCanvasElement::clearImageBuffer() const
597 {
598     ASSERT(m_hasCreatedImageBuffer);
599     ASSERT(!m_didClearImageBuffer);
600     ASSERT(m_context);
601
602     m_didClearImageBuffer = true;
603
604     if (m_context->is2d()) {
605         CanvasRenderingContext2D* context2D = static_cast<CanvasRenderingContext2D*>(m_context.get());
606         // No need to undo transforms/clip/etc. because we are called right after the context is reset.
607         context2D->clearRect(0, 0, width(), height());
608     }
609 }
610
611 void HTMLCanvasElement::clearCopiedImage()
612 {
613     m_copiedImage.clear();
614     m_didClearImageBuffer = false;
615 }
616
617 AffineTransform HTMLCanvasElement::baseTransform() const
618 {
619     ASSERT(m_hasCreatedImageBuffer);
620     FloatSize unscaledSize = size();
621     FloatSize deviceSize = convertLogicalToDevice(unscaledSize);
622     IntSize size(deviceSize.width(), deviceSize.height());
623     AffineTransform transform;
624     if (size.width() && size.height())
625         transform.scaleNonUniform(size.width() / unscaledSize.width(), size.height() / unscaledSize.height());
626     return m_imageBuffer->baseTransform() * transform;
627 }
628
629 }