1 /* 2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Alp Toker <alp (at) 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 "bindings/core/v8/ExceptionMessages.h" 32 #include "bindings/core/v8/ExceptionState.h" 33 #include "bindings/core/v8/ScriptController.h" 34 #include "core/HTMLNames.h" 35 #include "core/dom/Document.h" 36 #include "core/dom/ExceptionCode.h" 37 #include "core/frame/LocalFrame.h" 38 #include "core/frame/Settings.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/WebGLContextEvent.h" 44 #include "core/html/canvas/WebGLRenderingContext.h" 45 #include "core/rendering/RenderHTMLCanvas.h" 46 #include "core/rendering/RenderLayer.h" 47 #include "platform/MIMETypeRegistry.h" 48 #include "platform/RuntimeEnabledFeatures.h" 49 #include "platform/graphics/Canvas2DImageBufferSurface.h" 50 #include "platform/graphics/GraphicsContextStateSaver.h" 51 #include "platform/graphics/ImageBuffer.h" 52 #include "platform/graphics/RecordingImageBufferSurface.h" 53 #include "platform/graphics/UnacceleratedImageBufferSurface.h" 54 #include "platform/graphics/gpu/WebGLImageBufferSurface.h" 55 #include "platform/transforms/AffineTransform.h" 56 #include "public/platform/Platform.h" 57 #include <math.h> 58 #include <v8.h> 59 60 namespace blink { 61 62 using namespace HTMLNames; 63 64 // These values come from the WhatWG spec. 65 static const int DefaultWidth = 300; 66 static const int DefaultHeight = 150; 67 68 // Firefox limits width/height to 32767 pixels, but slows down dramatically before it 69 // reaches that limit. We limit by area instead, giving us larger maximum dimensions, 70 // in exchange for a smaller maximum canvas size. 71 static const int MaxCanvasArea = 32768 * 8192; // Maximum canvas area in CSS pixels 72 73 //In Skia, we will also limit width/height to 32767. 74 static const int MaxSkiaDim = 32767; // Maximum width/height in CSS pixels. 75 76 DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(CanvasObserver); 77 78 inline HTMLCanvasElement::HTMLCanvasElement(Document& document) 79 : HTMLElement(canvasTag, document) 80 , DocumentVisibilityObserver(document) 81 , m_size(DefaultWidth, DefaultHeight) 82 , m_ignoreReset(false) 83 , m_accelerationDisabled(false) 84 , m_externallyAllocatedMemory(0) 85 , m_originClean(true) 86 , m_didFailToCreateImageBuffer(false) 87 , m_didClearImageBuffer(false) 88 { 89 } 90 91 DEFINE_NODE_FACTORY(HTMLCanvasElement) 92 93 HTMLCanvasElement::~HTMLCanvasElement() 94 { 95 resetDirtyRect(); 96 v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(-m_externallyAllocatedMemory); 97 #if !ENABLE(OILPAN) 98 HashSet<RawPtr<CanvasObserver> >::iterator end = m_observers.end(); 99 for (HashSet<RawPtr<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it) 100 (*it)->canvasDestroyed(this); 101 // Ensure these go away before the ImageBuffer. 102 m_contextStateSaver.clear(); 103 m_context.clear(); 104 #endif 105 } 106 107 void HTMLCanvasElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 108 { 109 if (name == widthAttr || name == heightAttr) 110 reset(); 111 HTMLElement::parseAttribute(name, value); 112 } 113 114 RenderObject* HTMLCanvasElement::createRenderer(RenderStyle* style) 115 { 116 LocalFrame* frame = document().frame(); 117 if (frame && frame->script().canExecuteScripts(NotAboutToExecuteScript)) 118 return new RenderHTMLCanvas(this); 119 return HTMLElement::createRenderer(style); 120 } 121 122 Node::InsertionNotificationRequest HTMLCanvasElement::insertedInto(ContainerNode* node) 123 { 124 setIsInCanvasSubtree(true); 125 return HTMLElement::insertedInto(node); 126 } 127 128 void HTMLCanvasElement::addObserver(CanvasObserver* observer) 129 { 130 m_observers.add(observer); 131 } 132 133 void HTMLCanvasElement::removeObserver(CanvasObserver* observer) 134 { 135 m_observers.remove(observer); 136 } 137 138 void HTMLCanvasElement::setHeight(int value) 139 { 140 setIntegralAttribute(heightAttr, value); 141 } 142 143 void HTMLCanvasElement::setWidth(int value) 144 { 145 setIntegralAttribute(widthAttr, value); 146 } 147 148 CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs) 149 { 150 // A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing 151 // context is already 2D, just return that. If the existing context is WebGL, then destroy it 152 // before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a 153 // context with any other type string will destroy any existing context. 154 enum ContextType { 155 // Do not change assigned numbers of existing items: add new features to the end of the list. 156 Context2d = 0, 157 ContextExperimentalWebgl = 2, 158 ContextWebgl = 3, 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 setNeedsCompositingUpdate(); 172 } 173 return m_context.get(); 174 } 175 176 // Accept the the provisional "experimental-webgl" or official "webgl" context ID. 177 if (type == "webgl" || type == "experimental-webgl") { 178 ContextType contextType = (type == "webgl") ? ContextWebgl : ContextExperimentalWebgl; 179 if (!m_context) { 180 blink::Platform::current()->histogramEnumeration("Canvas.ContextType", contextType, ContextTypeCount); 181 m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs)); 182 setNeedsCompositingUpdate(); 183 updateExternallyAllocatedMemory(); 184 } else if (!m_context->is3d()) { 185 dispatchEvent(WebGLContextEvent::create(EventTypeNames::webglcontextcreationerror, false, true, "Canvas has an existing, non-WebGL context")); 186 return 0; 187 } 188 return m_context.get(); 189 } 190 191 return 0; 192 } 193 194 void HTMLCanvasElement::didDraw(const FloatRect& rect) 195 { 196 if (rect.isEmpty()) 197 return; 198 clearCopiedImage(); 199 if (m_dirtyRect.isEmpty()) 200 blink::Platform::current()->currentThread()->addTaskObserver(this); 201 m_dirtyRect.unite(rect); 202 } 203 204 void HTMLCanvasElement::didFinalizeFrame() 205 { 206 if (m_dirtyRect.isEmpty()) 207 return; 208 209 // Propagate the m_dirtyRect accumulated so far to the compositor 210 // before restarting with a blank dirty rect. 211 FloatRect srcRect(0, 0, size().width(), size().height()); 212 m_dirtyRect.intersect(srcRect); 213 if (RenderBox* ro = renderBox()) { 214 FloatRect mappedDirtyRect = mapRect(m_dirtyRect, srcRect, ro->contentBoxRect()); 215 // For querying RenderLayer::compositingState() 216 // FIXME: is this invalidation using the correct compositing state? 217 DisableCompositingQueryAsserts disabler; 218 ro->invalidatePaintRectangle(enclosingIntRect(mappedDirtyRect)); 219 } 220 notifyObserversCanvasChanged(m_dirtyRect); 221 blink::Platform::current()->currentThread()->removeTaskObserver(this); 222 m_dirtyRect = FloatRect(); 223 } 224 225 void HTMLCanvasElement::resetDirtyRect() 226 { 227 if (m_dirtyRect.isEmpty()) 228 return; 229 blink::Platform::current()->currentThread()->removeTaskObserver(this); 230 m_dirtyRect = FloatRect(); 231 } 232 233 void HTMLCanvasElement::didProcessTask() 234 { 235 // This method gets invoked if didDraw was called earlier in the current task. 236 ASSERT(!m_dirtyRect.isEmpty()); 237 if (is3D()) { 238 didFinalizeFrame(); 239 } else { 240 ASSERT(hasImageBuffer()); 241 m_imageBuffer->finalizeFrame(m_dirtyRect); 242 } 243 ASSERT(m_dirtyRect.isEmpty()); 244 } 245 246 void HTMLCanvasElement::willProcessTask() 247 { 248 ASSERT_NOT_REACHED(); 249 } 250 251 void HTMLCanvasElement::notifyObserversCanvasChanged(const FloatRect& rect) 252 { 253 WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator end = m_observers.end(); 254 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it) 255 (*it)->canvasChanged(this, rect); 256 } 257 258 void HTMLCanvasElement::reset() 259 { 260 if (m_ignoreReset) 261 return; 262 263 resetDirtyRect(); 264 265 bool ok; 266 bool hadImageBuffer = hasImageBuffer(); 267 268 int w = getAttribute(widthAttr).toInt(&ok); 269 if (!ok || w < 0) 270 w = DefaultWidth; 271 272 int h = getAttribute(heightAttr).toInt(&ok); 273 if (!ok || h < 0) 274 h = DefaultHeight; 275 276 if (m_contextStateSaver) { 277 // Reset to the initial graphics context state. 278 m_contextStateSaver->restore(); 279 m_contextStateSaver->save(); 280 } 281 282 if (m_context && m_context->is2d()) 283 toCanvasRenderingContext2D(m_context.get())->reset(); 284 285 IntSize oldSize = size(); 286 IntSize newSize(w, h); 287 288 // If the size of an existing buffer matches, we can just clear it instead of reallocating. 289 // This optimization is only done for 2D canvases for now. 290 if (hadImageBuffer && oldSize == newSize && m_context && m_context->is2d()) { 291 if (!m_didClearImageBuffer) 292 clearImageBuffer(); 293 return; 294 } 295 296 setSurfaceSize(newSize); 297 298 if (m_context && m_context->is3d() && oldSize != size()) 299 toWebGLRenderingContext(m_context.get())->reshape(width(), height()); 300 301 if (RenderObject* renderer = this->renderer()) { 302 if (renderer->isCanvas()) { 303 if (oldSize != size()) { 304 toRenderHTMLCanvas(renderer)->canvasSizeChanged(); 305 if (renderBox() && renderBox()->hasAcceleratedCompositing()) 306 renderBox()->contentChanged(CanvasChanged); 307 } 308 if (hadImageBuffer) 309 renderer->setShouldDoFullPaintInvalidation(true); 310 } 311 } 312 313 WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator end = m_observers.end(); 314 for (WillBeHeapHashSet<RawPtrWillBeWeakMember<CanvasObserver> >::iterator it = m_observers.begin(); it != end; ++it) 315 (*it)->canvasResized(this); 316 } 317 318 bool HTMLCanvasElement::paintsIntoCanvasBuffer() const 319 { 320 ASSERT(m_context); 321 322 if (!m_context->isAccelerated()) 323 return true; 324 325 if (renderBox() && renderBox()->hasAcceleratedCompositing()) 326 return false; 327 328 return true; 329 } 330 331 332 void HTMLCanvasElement::paint(GraphicsContext* context, const LayoutRect& r) 333 { 334 if (m_context) { 335 if (!paintsIntoCanvasBuffer() && !document().printing()) 336 return; 337 m_context->paintRenderingResultsToCanvas(); 338 } 339 340 if (hasImageBuffer()) { 341 ImageBuffer* imageBuffer = buffer(); 342 if (imageBuffer) { 343 CompositeOperator compositeOperator = !m_context || m_context->hasAlpha() ? CompositeSourceOver : CompositeCopy; 344 if (m_presentedImage) 345 context->drawImage(m_presentedImage.get(), pixelSnappedIntRect(r), compositeOperator, DoNotRespectImageOrientation); 346 else 347 context->drawImageBuffer(imageBuffer, pixelSnappedIntRect(r), 0, compositeOperator); 348 } 349 } else { 350 // When alpha is false, we should draw to opaque black. 351 if (m_context && !m_context->hasAlpha()) 352 context->fillRect(FloatRect(r), Color(0, 0, 0)); 353 } 354 355 if (is3D()) 356 toWebGLRenderingContext(m_context.get())->markLayerComposited(); 357 } 358 359 bool HTMLCanvasElement::is3D() const 360 { 361 return m_context && m_context->is3d(); 362 } 363 364 void HTMLCanvasElement::makePresentationCopy() 365 { 366 if (!m_presentedImage) { 367 // The buffer contains the last presented data, so save a copy of it. 368 m_presentedImage = buffer()->copyImage(CopyBackingStore, Unscaled); 369 updateExternallyAllocatedMemory(); 370 } 371 } 372 373 void HTMLCanvasElement::clearPresentationCopy() 374 { 375 m_presentedImage.clear(); 376 updateExternallyAllocatedMemory(); 377 } 378 379 void HTMLCanvasElement::setSurfaceSize(const IntSize& size) 380 { 381 m_size = size; 382 m_didFailToCreateImageBuffer = false; 383 discardImageBuffer(); 384 clearCopiedImage(); 385 if (m_context && m_context->is2d()) { 386 CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get()); 387 if (context2d->isContextLost()) { 388 context2d->restoreContext(); 389 } 390 } 391 } 392 393 String HTMLCanvasElement::toEncodingMimeType(const String& mimeType) 394 { 395 String lowercaseMimeType = mimeType.lower(); 396 397 // FIXME: Make isSupportedImageMIMETypeForEncoding threadsafe (to allow this method to be used on a worker thread). 398 if (mimeType.isNull() || !MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(lowercaseMimeType)) 399 lowercaseMimeType = "image/png"; 400 401 return lowercaseMimeType; 402 } 403 404 const AtomicString HTMLCanvasElement::imageSourceURL() const 405 { 406 return AtomicString(toDataURLInternal("image/png", 0, true)); 407 } 408 409 String HTMLCanvasElement::toDataURLInternal(const String& mimeType, const double* quality, bool isSaving) const 410 { 411 if (m_size.isEmpty() || !buffer()) 412 return String("data:,"); 413 414 String encodingMimeType = toEncodingMimeType(mimeType); 415 416 // Try to get ImageData first, as that may avoid lossy conversions. 417 RefPtrWillBeRawPtr<ImageData> imageData = getImageData(); 418 419 if (imageData) 420 return ImageDataToDataURL(ImageDataBuffer(imageData->size(), imageData->data()), encodingMimeType, quality); 421 422 if (m_context && m_context->is3d()) { 423 toWebGLRenderingContext(m_context.get())->setSavingImage(isSaving); 424 m_context->paintRenderingResultsToCanvas(); 425 toWebGLRenderingContext(m_context.get())->setSavingImage(false); 426 } 427 428 return buffer()->toDataURL(encodingMimeType, quality); 429 } 430 431 String HTMLCanvasElement::toDataURL(const String& mimeType, const double* quality, ExceptionState& exceptionState) const 432 { 433 if (!m_originClean) { 434 exceptionState.throwSecurityError("Tainted canvases may not be exported."); 435 return String(); 436 } 437 438 return toDataURLInternal(mimeType, quality); 439 } 440 441 PassRefPtrWillBeRawPtr<ImageData> HTMLCanvasElement::getImageData() const 442 { 443 if (!m_context || !m_context->is3d()) 444 return nullptr; 445 return toWebGLRenderingContext(m_context.get())->paintRenderingResultsToImageData(); 446 } 447 448 SecurityOrigin* HTMLCanvasElement::securityOrigin() const 449 { 450 return document().securityOrigin(); 451 } 452 453 bool HTMLCanvasElement::shouldAccelerate(const IntSize& size) const 454 { 455 if (m_context && !m_context->is2d()) 456 return false; 457 458 if (m_accelerationDisabled) 459 return false; 460 461 Settings* settings = document().settings(); 462 if (!settings || !settings->accelerated2dCanvasEnabled()) 463 return false; 464 465 // Do not use acceleration for small canvas. 466 if (size.width() * size.height() < settings->minimumAccelerated2dCanvasSize()) 467 return false; 468 469 if (!blink::Platform::current()->canAccelerate2dCanvas()) 470 return false; 471 472 return true; 473 } 474 475 PassOwnPtr<ImageBufferSurface> HTMLCanvasElement::createImageBufferSurface(const IntSize& deviceSize, int* msaaSampleCount) 476 { 477 OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque; 478 479 *msaaSampleCount = 0; 480 if (is3D()) { 481 // If 3d, but the use of the canvas will be for non-accelerated content 482 // (such as -webkit-canvas, then then make a non-accelerated 483 // ImageBuffer. This means copying the internal Image will require a 484 // pixel readback, but that is unavoidable in this case. 485 // FIXME: Actually, avoid setting m_accelerationDisabled at all when 486 // doing GPU-based rasterization. 487 if (m_accelerationDisabled) 488 return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode)); 489 return adoptPtr(new WebGLImageBufferSurface(size(), opacityMode)); 490 } 491 492 if (RuntimeEnabledFeatures::displayList2dCanvasEnabled()) { 493 OwnPtr<ImageBufferSurface> surface = adoptPtr(new RecordingImageBufferSurface(size(), opacityMode)); 494 if (surface->isValid()) 495 return surface.release(); 496 } 497 498 if (shouldAccelerate(deviceSize)) { 499 if (document().settings()) 500 *msaaSampleCount = document().settings()->accelerated2dCanvasMSAASampleCount(); 501 OwnPtr<ImageBufferSurface> surface = adoptPtr(new Canvas2DImageBufferSurface(size(), opacityMode, *msaaSampleCount)); 502 if (surface->isValid()) 503 return surface.release(); 504 } 505 506 return adoptPtr(new UnacceleratedImageBufferSurface(size(), opacityMode)); 507 } 508 509 void HTMLCanvasElement::createImageBuffer() 510 { 511 createImageBufferInternal(); 512 if (m_didFailToCreateImageBuffer && m_context && m_context->is2d()) 513 toCanvasRenderingContext2D(m_context.get())->loseContext(); 514 } 515 516 void HTMLCanvasElement::createImageBufferInternal() 517 { 518 ASSERT(!m_imageBuffer); 519 ASSERT(!m_contextStateSaver); 520 521 m_didFailToCreateImageBuffer = true; 522 m_didClearImageBuffer = true; 523 524 IntSize deviceSize = size(); 525 if (deviceSize.width() * deviceSize.height() > MaxCanvasArea) 526 return; 527 528 if (deviceSize.width() > MaxSkiaDim || deviceSize.height() > MaxSkiaDim) 529 return; 530 531 if (!deviceSize.width() || !deviceSize.height()) 532 return; 533 534 int msaaSampleCount; 535 OwnPtr<ImageBufferSurface> surface = createImageBufferSurface(deviceSize, &msaaSampleCount); 536 if (!surface->isValid()) 537 return; 538 539 m_imageBuffer = ImageBuffer::create(surface.release()); 540 m_imageBuffer->setClient(this); 541 542 m_didFailToCreateImageBuffer = false; 543 544 updateExternallyAllocatedMemory(); 545 546 if (is3D()) { 547 // Early out for WebGL canvases 548 return; 549 } 550 551 m_imageBuffer->setClient(this); 552 m_imageBuffer->context()->setShouldClampToSourceRect(false); 553 m_imageBuffer->context()->disableAntialiasingOptimizationForHairlineImages(); 554 m_imageBuffer->context()->setImageInterpolationQuality(CanvasDefaultInterpolationQuality); 555 // Enabling MSAA overrides a request to disable antialiasing. This is true regardless of whether the 556 // rendering mode is accelerated or not. For consistency, we don't want to apply AA in accelerated 557 // canvases but not in unaccelerated canvases. 558 if (!msaaSampleCount && document().settings() && !document().settings()->antialiased2dCanvasEnabled()) 559 m_imageBuffer->context()->setShouldAntialias(false); 560 // GraphicsContext's defaults don't always agree with the 2d canvas spec. 561 // See CanvasRenderingContext2D::State::State() for more information. 562 m_imageBuffer->context()->setMiterLimit(10); 563 m_imageBuffer->context()->setStrokeThickness(1); 564 #if ENABLE(ASSERT) 565 m_imageBuffer->context()->disableDestructionChecks(); // 2D canvas is allowed to leave context in an unfinalized state. 566 #endif 567 m_contextStateSaver = adoptPtr(new GraphicsContextStateSaver(*m_imageBuffer->context())); 568 569 if (m_context) 570 setNeedsCompositingUpdate(); 571 } 572 573 void HTMLCanvasElement::notifySurfaceInvalid() 574 { 575 if (m_context && m_context->is2d()) { 576 CanvasRenderingContext2D* context2d = toCanvasRenderingContext2D(m_context.get()); 577 context2d->loseContext(); 578 } 579 } 580 581 void HTMLCanvasElement::trace(Visitor* visitor) 582 { 583 #if ENABLE(OILPAN) 584 visitor->trace(m_observers); 585 visitor->trace(m_context); 586 #endif 587 DocumentVisibilityObserver::trace(visitor); 588 HTMLElement::trace(visitor); 589 } 590 591 void HTMLCanvasElement::updateExternallyAllocatedMemory() const 592 { 593 int bufferCount = 0; 594 if (m_imageBuffer) 595 bufferCount++; 596 if (is3D()) 597 bufferCount += 2; 598 if (m_copiedImage) 599 bufferCount++; 600 if (m_presentedImage) 601 bufferCount++; 602 603 Checked<intptr_t, RecordOverflow> checkedExternallyAllocatedMemory = 4 * bufferCount; 604 checkedExternallyAllocatedMemory *= width(); 605 checkedExternallyAllocatedMemory *= height(); 606 intptr_t externallyAllocatedMemory; 607 if (checkedExternallyAllocatedMemory.safeGet(externallyAllocatedMemory) == CheckedState::DidOverflow) 608 externallyAllocatedMemory = std::numeric_limits<intptr_t>::max(); 609 610 // Subtracting two intptr_t that are known to be positive will never underflow. 611 v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(externallyAllocatedMemory - m_externallyAllocatedMemory); 612 m_externallyAllocatedMemory = externallyAllocatedMemory; 613 } 614 615 GraphicsContext* HTMLCanvasElement::drawingContext() const 616 { 617 return buffer() ? m_imageBuffer->context() : 0; 618 } 619 620 GraphicsContext* HTMLCanvasElement::existingDrawingContext() const 621 { 622 if (!hasImageBuffer()) 623 return 0; 624 625 return drawingContext(); 626 } 627 628 ImageBuffer* HTMLCanvasElement::buffer() const 629 { 630 if (!hasImageBuffer() && !m_didFailToCreateImageBuffer) 631 const_cast<HTMLCanvasElement*>(this)->createImageBuffer(); 632 return m_imageBuffer.get(); 633 } 634 635 void HTMLCanvasElement::ensureUnacceleratedImageBuffer() 636 { 637 if ((hasImageBuffer() && !m_imageBuffer->isAccelerated()) || m_didFailToCreateImageBuffer) 638 return; 639 discardImageBuffer(); 640 OpacityMode opacityMode = !m_context || m_context->hasAlpha() ? NonOpaque : Opaque; 641 m_imageBuffer = ImageBuffer::create(size(), opacityMode); 642 m_didFailToCreateImageBuffer = !m_imageBuffer; 643 } 644 645 Image* HTMLCanvasElement::copiedImage() const 646 { 647 if (!m_copiedImage && buffer()) { 648 if (m_context && m_context->is3d()) { 649 toWebGLRenderingContext(m_context.get())->setSavingImage(true); 650 m_context->paintRenderingResultsToCanvas(); 651 toWebGLRenderingContext(m_context.get())->setSavingImage(false); 652 } 653 m_copiedImage = buffer()->copyImage(CopyBackingStore, Unscaled); 654 updateExternallyAllocatedMemory(); 655 } 656 return m_copiedImage.get(); 657 } 658 659 void HTMLCanvasElement::clearImageBuffer() 660 { 661 ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer); 662 ASSERT(!m_didClearImageBuffer); 663 ASSERT(m_context); 664 665 m_didClearImageBuffer = true; 666 667 if (m_context->is2d()) { 668 // No need to undo transforms/clip/etc. because we are called right 669 // after the context is reset. 670 toCanvasRenderingContext2D(m_context.get())->clearRect(0, 0, width(), height()); 671 } 672 } 673 674 void HTMLCanvasElement::discardImageBuffer() 675 { 676 m_contextStateSaver.clear(); // uses context owned by m_imageBuffer 677 m_imageBuffer.clear(); 678 resetDirtyRect(); 679 updateExternallyAllocatedMemory(); 680 } 681 682 bool HTMLCanvasElement::hasValidImageBuffer() const 683 { 684 return m_imageBuffer && m_imageBuffer->isSurfaceValid(); 685 } 686 687 void HTMLCanvasElement::clearCopiedImage() 688 { 689 if (m_copiedImage) { 690 m_copiedImage.clear(); 691 updateExternallyAllocatedMemory(); 692 } 693 m_didClearImageBuffer = false; 694 } 695 696 AffineTransform HTMLCanvasElement::baseTransform() const 697 { 698 ASSERT(hasImageBuffer() && !m_didFailToCreateImageBuffer); 699 return m_imageBuffer->baseTransform(); 700 } 701 702 void HTMLCanvasElement::didChangeVisibilityState(PageVisibilityState visibility) 703 { 704 if (!m_context) 705 return; 706 bool hidden = visibility != PageVisibilityStateVisible; 707 m_context->setIsHidden(hidden); 708 if (hidden) { 709 clearCopiedImage(); 710 if (is3D()) { 711 discardImageBuffer(); 712 } 713 } 714 } 715 716 void HTMLCanvasElement::didMoveToNewDocument(Document& oldDocument) 717 { 718 setObservedDocument(document()); 719 HTMLElement::didMoveToNewDocument(oldDocument); 720 } 721 722 PassRefPtr<Image> HTMLCanvasElement::getSourceImageForCanvas(SourceImageMode mode, SourceImageStatus* status) const 723 { 724 if (!width() || !height()) { 725 *status = ZeroSizeCanvasSourceImageStatus; 726 return nullptr; 727 } 728 729 if (!buffer()) { 730 *status = InvalidSourceImageStatus; 731 return nullptr; 732 } 733 734 if (mode == CopySourceImageIfVolatile) { 735 *status = NormalSourceImageStatus; 736 return copiedImage(); 737 } 738 739 if (m_context && m_context->is3d()) { 740 m_context->paintRenderingResultsToCanvas(); 741 *status = ExternalSourceImageStatus; 742 } else { 743 *status = NormalSourceImageStatus; 744 } 745 return m_imageBuffer->copyImage(DontCopyBackingStore, Unscaled); 746 } 747 748 bool HTMLCanvasElement::wouldTaintOrigin(SecurityOrigin*) const 749 { 750 return !originClean(); 751 } 752 753 FloatSize HTMLCanvasElement::sourceSize() const 754 { 755 return FloatSize(width(), height()); 756 } 757 758 } 759