Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/html/HTMLCanvasElement.h"
30
31 #include <math.h>
32 #include "HTMLNames.h"
33 #include "RuntimeEnabledFeatures.h"
34 #include "bindings/v8/ExceptionMessages.h"
35 #include "bindings/v8/ExceptionState.h"
36 #include "bindings/v8/ScriptController.h"
37 #include "core/dom/Document.h"
38 #include "core/dom/ExceptionCode.h"
39 #include "core/html/ImageData.h"
40 #include "core/html/canvas/Canvas2DContextAttributes.h"
41 #include "core/html/canvas/CanvasRenderingContext2D.h"
42 #include "core/html/canvas/WebGLContextAttributes.h"
43 #include "core/html/canvas/WebGLRenderingContext.h"
44 #include "core/frame/Frame.h"
45 #include "core/frame/Settings.h"
46 #include "core/rendering/RenderHTMLCanvas.h"
47 #include "platform/MIMETypeRegistry.h"
48 #include "platform/graphics/Canvas2DImageBufferSurface.h"
49 #include "platform/graphics/GraphicsContextStateSaver.h"
50 #include "platform/graphics/ImageBuffer.h"
51 #include "platform/graphics/UnacceleratedImageBufferSurface.h"
52 #include "platform/graphics/gpu/WebGLImageBufferSurface.h"
53 #include "platform/transforms/AffineTransform.h"
54 #include "public/platform/Platform.h"
55
56 namespace WebCore {
57
58 using namespace HTMLNames;
59
60 // These values come from the WhatWG spec.
61 static const int DefaultWidth = 300;
62 static const int DefaultHeight = 150;
63
64 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it
65 // reaches that limit. We limit by area instead, giving us larger maximum dimensions,
66 // in exchange for a smaller maximum canvas size.
67 static const int MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels
68
69 //In Skia, we will also limit width/height to 32767.
70 static const int MaxSkiaDim = 32767; // Maximum width/height in CSS pixels.
71
72 HTMLCanvasElement::HTMLCanvasElement(Document& document)
73     : HTMLElement(canvasTag, document)
74     , DocumentVisibilityObserver(document)
75     , m_size(DefaultWidth, DefaultHeight)
76     , m_rendererIsCanvas(false)
77     , m_ignoreReset(false)
78     , m_accelerationDisabled(false)
79     , m_externallyAllocatedMemory(0)
80     , m_originClean(true)
81     , m_didFailToCreateImageBuffer(false)
82     , m_didClearImageBuffer(false)
83 {
84     ScriptWrappable::init(this);
85 }
86
87 PassRefPtr<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
88 {
89     return adoptRef(new HTMLCanvasElement(document));
90 }
91
92 HTMLCanvasElement::~HTMLCanvasElement()
93 {
94     setExternallyAllocatedMemory(0);
95     HashSet<CanvasObserver*>::iterator end = m_observers.end();
96     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
97         (*it)->canvasDestroyed(this);
98
99     m_context.clear(); // Ensure this goes away before the ImageBuffer.
100 }
101
102 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
103 {
104     if (name == widthAttr || name == heightAttr)
105         reset();
106     HTMLElement::parseAttribute(name, value);
107 }
108
109 RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style)
110 {
111     Frame* frame = document().frame();
112     if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) {
113         m_rendererIsCanvas = true;
114         return new RenderHTMLCanvas(this);
115     }
116
117     m_rendererIsCanvas = false;
118     return HTMLElement::createRenderer(style);
119 }
120
121 Node::InsertionNotificationRequest HTMLCanvasElement::insertedInto(ContainerNode* node)
122 {
123     setIsInCanvasSubtree(true);
124     return HTMLElement::insertedInto(node);
125 }
126
127 void HTMLCanvasElement::addObserver(CanvasObserver* observer)
128 {
129     m_observers.add(observer);
130 }
131
132 void HTMLCanvasElement::removeObserver(CanvasObserver* observer)
133 {
134     m_observers.remove(observer);
135 }
136
137 void HTMLCanvasElement::setHeight(int value)
138 {
139     setIntegralAttribute(heightAttr, value);
140 }
141
142 void HTMLCanvasElement::setWidth(int value)
143 {
144     setIntegralAttribute(widthAttr, value);
145 }
146
147 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
148 {
149     // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
150     // context is already 2D, just return that. If the existing context is WebGL, then destroy it
151     // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
152     // context with any other type string will destroy any existing context.
153     enum ContextType {
154         Context2d,
155         ContextWebkit3d,
156         ContextExperimentalWebgl,
157         ContextWebgl,
158         // Only add new items to the end and keep the order of existing items.
159         ContextTypeCount,
160     };
161
162     // FIXME - The code depends on the context not going away once created, to prevent JS from
163     // seeing a dangling pointer. So for now we will disallow the context from being changed
164     // once it is created.
165     if (type == "2d") {
166         if (m_context && !m_context->is2d())
167             return 0;
168         if (!m_context) {
169             blink::Platform::current()->histogramEnumeration("Canvas.ContextType", Context2d, ContextTypeCount);
170             m_context = CanvasRenderingContext2D::create(this, static_cast<Canvas2DContextAttributes*>(attrs), document().inQuirksMode());
171             if (m_context)
172                 scheduleLayerUpdate();
173         }
174         return m_context.get();
175     }
176
177     Settings* settings = document().settings();
178     if (settings && settings->webGLEnabled()) {
179         // Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
180         // Now that WebGL is ratified, we will also accept "webgl" as the context name in Chrome.
181         ContextType contextType;
182         bool is3dContext = true;
183         if (type == "webkit-3d")
184             contextType = ContextWebkit3d;
185         else if (type == "experimental-webgl")
186             contextType = ContextExperimentalWebgl;
187         else if (type == "webgl")
188             contextType = ContextWebgl;
189         else
190             is3dContext = false;
191
192         if (is3dContext) {
193             if (m_context && !m_context->is3d())
194                 return 0;
195             if (!m_context) {
196                 blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount);
197                 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
198                 if (m_context)
199                     scheduleLayerUpdate();
200             }
201             return m_context.get();
202         }
203     }
204     return 0;
205 }
206
207 void HTMLCanvasElement::didDraw(const FloatRect& rect)
208 {
209     clearCopiedImage();
210
211     if (RenderBox* ro = renderBox()) {
212         FloatRect destRect = ro->contentBoxRect();
213         FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
214         r.intersect(destRect);
215         if (r.isEmpty() || m_dirtyRect.contains(r))
216             return;
217
218         m_dirtyRect.unite(r);
219         ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
220     }
221
222     notifyObserversCanvasChanged(rect);
223 }
224
225 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect)
226 {
227     HashSet<CanvasObserver*>::iterator end = m_observers.end();
228     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
229         (*it)->canvasChanged(this, rect);
230 }
231
232 void HTMLCanvasElement::reset()
233 {
234     if (m_ignoreReset)
235         return;
236
237     bool ok;
238     bool hadImageBuffer = hasImageBuffer();
239
240     int w = getAttribute(widthAttr).toInt(&ok);
241     if (!ok || w < 0)
242         w = DefaultWidth;
243
244     int h = getAttribute(heightAttr).toInt(&ok);
245     if (!ok || h < 0)
246         h = DefaultHeight;
247
248     if (m_contextStateSaver) {
249         // Reset to the initial graphics context state.
250         m_contextStateSaver->restore();
251         m_contextStateSaver->save();
252     }
253
254     if (m_context && m_context->is2d())
255         toCanvasRenderingContext2D(m_context.get())->reset();
256
257     IntSize oldSize = size();
258     IntSize newSize(w, h);
259
260     // If the size of an existing buffer matches, we can just clear it instead of reallocating.
261     // This optimization is only done for 2D canvases for now.
262     if (hadImageBuffer && oldSize == newSize && m_context && m_context->is2d()) {
263         if (!m_didClearImageBuffer)
264             clearImageBuffer();
265         return;
266     }
267
268     setSurfaceSize(newSize);
269
270     if (m_context && m_context->is3d() && oldSize != size())
271         toWebGLRenderingContext(m_context.get())->reshape(width(), height());
272
273     if (RenderObject* renderer = this->renderer()) {
274         if (m_rendererIsCanvas) {
275             if (oldSize != size()) {
276                 toRenderHTMLCanvas(renderer)->canvasSizeChanged();
277                 if (renderBox() && renderBox()->hasAcceleratedCompositing())
278                     renderBox()->contentChanged(CanvasChanged);
279             }
280             if (hadImageBuffer)
281                 renderer->repaint();
282         }
283     }
284
285     HashSet<CanvasObserver*>::iterator end = m_observers.end();
286     for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
287         (*it)->canvasResized(this);
288 }
289
290 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const
291 {
292     ASSERT(m_context);
293
294     if (!m_context->isAccelerated())
295         return true;
296
297     if (renderBox() && renderBox()->hasAcceleratedCompositing())
298         return false;
299
300     return true;
301 }
302
303
304 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r, bool useLowQualityScale)
305 {
306     // Clear the dirty rect
307     m_dirtyRect = FloatRect();
308
309     if (context->paintingDisabled())
310         return;
311
312     if (m_context) {
313         if (!paintsIntoCanvasBuffer() && !document().printing())
314             return;
315         m_context->paintRenderingResultsToCanvas();
316     }
317
318     if (hasImageBuffer()) {
319         ImageBuffer* imageBuffer = buffer();
320         if (imageBuffer) {
321             CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy;
322             if (m_presentedImage)
323                 context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation, useLowQualityScale);
324             else
325                 context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), compositeOperator, blink::WebBlendModeNormal, useLowQualityScale);
326         }
327     }
328
329     if (is3D())
330         toWebGLRenderingContext(m_context.get())->markLayerComposited();
331 }
332
333 bool HTMLCanvasElement::is3D() const
334 {
335     return m_context && m_context->is3d();
336 }
337
338 void HTMLCanvasElement::makePresentationCopy()
339 {
340     if (!m_presentedImage) {
341         // The buffer contains the last presented data, so save a copy of it.
342         m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
343     }
344 }
345
346 void HTMLCanvasElement::clearPresentationCopy()
347 {
348     m_presentedImage.clear();
349 }
350
351 void HTMLCanvasElement::setSurfaceSize(const IntSize& size)
352 {
353     m_size = size;
354     m_didFailToCreateImageBuffer = false;
355     discardImageBuffer();
356     setExternallyAllocatedMemory(0);
357     clearCopiedImage();
358 }
359
360 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType)
361 {
362     String lowercaseMimeType = mimeType.lower();
363
364     // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread).
365     if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType))
366         lowercaseMimeType = "image/png";
367
368     return lowercaseMimeType;
369 }
370
371 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState)
372 {
373     if (!m_originClean) {
374         exceptionState.throwSecurityError("Tainted canvases may not be exported.");
375         return String();
376     }
377
378     if (m_size.isEmpty() || !buffer())
379         return String("data:,");
380
381     String encodingMimeType = toEncodingMimeType(mimeType);
382
383     // Try to get ImageData first, as that may avoid lossy conversions.
384     RefPtr<ImageData> imageData = getImageData();
385
386     if (imageData)
387         return ImageDataToDataURL(ImageDataBuffer(imageData->size(), imageData->data()), encodingMimeType, quality);
388
389     if (m_context)
390         m_context->paintRenderingResultsToCanvas();
391
392     return buffer()->toDataURL(encodingMimeType, quality);
393 }
394
395 PassRefPtr<ImageData> HTMLCanvasElement::getImageData()
396 {
397     if (!m_context || !m_context->is3d())
398        return 0;
399     return toWebGLRenderingContext(m_context.get())->paintRenderingResultsToImageData();
400 }
401
402 SecurityOrigin* HTMLCanvasElement::securityOrigin() const
403 {
404     return document().securityOrigin();
405 }
406
407 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const
408 {
409     if (m_context && !m_context->is2d())
410         return false;
411
412     if (m_accelerationDisabled)
413         return false;
414
415     Settings* settings = document().settings();
416     if (!settings || !settings->accelerated2dCanvasEnabled())
417         return false;
418
419     // Do not use acceleration for small canvas.
420     if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize())
421         return false;
422
423     if (!blink::Platform::current()->canAccelerate2dCanvas())
424         return false;
425
426     return true;
427 }
428
429 PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount)
430 {
431     OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
432
433     *msaaSampleCount = 0;
434     if (is3D())
435         return adoptPtr(new WebGLImageBufferSurface(size(), opacityMode));
436
437     if (shouldAccelerate(deviceSize)) {
438         if (document().settings())
439             *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount();
440         OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size(), opacityMode, *msaaSampleCount));
441         if (surface->isValid())
442             return surface.release();
443     }
444
445     return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode));
446 }
447
448 void HTMLCanvasElement::createImageBuffer()
449 {
450     ASSERT(!m_imageBuffer);
451     ASSERT(!m_contextStateSaver);
452
453     m_didFailToCreateImageBuffer = true;
454     m_didClearImageBuffer = true;
455
456     IntSize deviceSize = size();
457     if (deviceSize.width() * deviceSize.height() > MaxCanvasArea)
458         return;
459
460     if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim)
461         return;
462
463     if (!deviceSize.width() || !deviceSize.height())
464         return;
465
466     int msaaSampleCount;
467     OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount);
468     if (!surface->isValid())
469         return;
470     m_imageBuffer = ImageBuffer::create(surface.release());
471
472     m_didFailToCreateImageBuffer = false;
473
474     setExternallyAllocatedMemory(4 * width() * height());
475
476     if (is3D()) {
477         // Early out for WebGL canvases
478         return;
479     }
480
481     m_imageBuffer->context()->setShouldClampToSourceRect(false);
482     m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
483     // Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the
484     // rendering mode is accelerated or not. For consistency, we don't want to apply AA in accelerated
485     // canvases but not in unaccelerated canvases.
486     if (!msaaSampleCount && document().settings() && !document().settings()->antialiased2dCanvasEnabled())
487         m_imageBuffer->context()->setShouldAntialias(false);
488     // GraphicsContext's defaults don't always agree with the 2d canvas spec.
489     // See CanvasRenderingContext2D::State::State() for more information.
490     m_imageBuffer->context()->setMiterLimit(10);
491     m_imageBuffer->context()->setStrokeThickness(1);
492     m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context()));
493
494     // Recalculate compositing requirements if acceleration state changed.
495     if (m_context)
496         scheduleLayerUpdate();
497 }
498
499 void HTMLCanvasElement::setExternallyAllocatedMemory(intptr_t externallyAllocatedMemory)
500 {
501     v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory);
502     m_externallyAllocatedMemory = externallyAllocatedMemory;
503 }
504
505 GraphicsContext* HTMLCanvasElement::drawingContext() const
506 {
507     return buffer() ? m_imageBuffer->context() : 0;
508 }
509
510 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const
511 {
512     if (m_didFailToCreateImageBuffer) {
513         ASSERT(!hasImageBuffer());
514         return 0;
515     }
516
517     return drawingContext();
518 }
519
520 ImageBuffer* HTMLCanvasElement::buffer() const
521 {
522     if (!hasImageBuffer() && !m_didFailToCreateImageBuffer)
523         const_cast<HTMLCanvasElement*>(this)->createImageBuffer();
524     return m_imageBuffer.get();
525 }
526
527 void HTMLCanvasElement::ensureUnacceleratedImageBuffer()
528 {
529     if ((hasImageBuffer() && !m_imageBuffer->isAccelerated()) || m_didFailToCreateImageBuffer)
530         return;
531     discardImageBuffer();
532     OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque;
533     m_imageBuffer = ImageBuffer::create(size(), opacityMode);
534     m_didFailToCreateImageBuffer = !m_imageBuffer;
535 }
536
537 Image* HTMLCanvasElement::copiedImage() const
538 {
539     if (!m_copiedImage && buffer()) {
540         if (m_context)
541             m_context->paintRenderingResultsToCanvas();
542         m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled);
543     }
544     return m_copiedImage.get();
545 }
546
547 void HTMLCanvasElement::clearImageBuffer()
548 {
549     ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
550     ASSERT(!m_didClearImageBuffer);
551     ASSERT(m_context);
552
553     m_didClearImageBuffer = true;
554
555     if (m_context->is2d()) {
556         // No need to undo transforms/clip/etc. because we are called right
557         // after the context is reset.
558         toCanvasRenderingContext2D(m_context.get())->clearRect(0, 0, width(), height());
559     }
560 }
561
562 void HTMLCanvasElement::discardImageBuffer()
563 {
564     m_contextStateSaver.clear(); // uses context owned by m_imageBuffer
565     m_imageBuffer.clear();
566 }
567
568 void HTMLCanvasElement::clearCopiedImage()
569 {
570     m_copiedImage.clear();
571     m_didClearImageBuffer = false;
572 }
573
574 AffineTransform HTMLCanvasElement::baseTransform() const
575 {
576     ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer);
577     return m_imageBuffer->baseTransform();
578 }
579
580 void HTMLCanvasElement::didChangeVisibilityState(PageVisibilityState visibility)
581 {
582     if (hasImageBuffer()) {
583         bool hidden = visibility != PageVisibilityStateVisible;
584         if (hidden) {
585             clearCopiedImage();
586             if (is3D()) {
587                 discardImageBuffer();
588             }
589         }
590         if (hasImageBuffer()) {
591             m_imageBuffer->setIsHidden(hidden);
592         }
593     }
594 }
595
596 void HTMLCanvasElement::didMoveToNewDocument(Document& oldDocument)
597 {
598     setObservedDocument(document());
599     HTMLElement::didMoveToNewDocument(oldDocument);
600 }
601
602 }