| /* |
| * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Alp Toker <alp@atoker.com> |
| * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" |
| |
| #include <math.h> |
| |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/checked_math.h" |
| #include "build/build_config.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_metric_builder.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h" |
| #include "third_party/blink/public/common/privacy_budget/identifiable_surface.h" |
| #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/mojom/gpu/gpu.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/public/resources/grit/blink_image_resources.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_controller.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_encode_options.h" |
| #include "third_party/blink/renderer/core/css/css_font_selector.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/fileapi/file.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_context_creation_attributes_core.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_draw_listener.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_font_cache.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h" |
| #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_factory.h" |
| #include "third_party/blink/renderer/core/html/canvas/image_data.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_select_element.h" |
| #include "third_party/blink/renderer/core/html/html_image_element.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" |
| #include "third_party/blink/renderer/core/input_type_names.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_canvas_result.h" |
| #include "third_party/blink/renderer/core/layout/layout_html_canvas.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/probe/core_probes.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/image_data_buffer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h" |
| #include "third_party/blink/renderer/platform/image-encoders/image_encoder_utils.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "ui/base/resource/scale_factor.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // These values come from the WhatWG spec. |
| constexpr int kDefaultCanvasWidth = 300; |
| constexpr int kDefaultCanvasHeight = 150; |
| |
| // A default value of quality argument for toDataURL and toBlob |
| // It is in an invalid range (outside 0.0 - 1.0) so that it will not be |
| // misinterpreted as a user-input value |
| constexpr int kUndefinedQualityValue = -1.0; |
| constexpr int kMinimumAccelerated2dCanvasSize = 128 * 129; |
| |
| // A default size used for canvas memory allocation when canvas size is greater |
| // than 2^20. |
| constexpr uint32_t kMaximumCanvasSize = 2 << 20; |
| |
| } // namespace |
| |
| HTMLCanvasElement::HTMLCanvasElement(Document& document) |
| : HTMLElement(html_names::kCanvasTag, document), |
| ExecutionContextLifecycleObserver(GetExecutionContext()), |
| PageVisibilityObserver(document.GetPage()), |
| CanvasRenderingContextHost( |
| CanvasRenderingContextHost::HostType::kCanvasHost), |
| size_(kDefaultCanvasWidth, kDefaultCanvasHeight), |
| context_creation_was_blocked_(false), |
| ignore_reset_(false), |
| origin_clean_(true), |
| surface_layer_bridge_(nullptr), |
| externally_allocated_memory_(0) { |
| UseCounter::Count(document, WebFeature::kHTMLCanvasElement); |
| GetDocument().IncrementNumberOfCanvases(); |
| } |
| |
| HTMLCanvasElement::~HTMLCanvasElement() { |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory( |
| -externally_allocated_memory_); |
| } |
| |
| void HTMLCanvasElement::Dispose() { |
| // We need to record metrics before we dispose of anything |
| if (context_) |
| UMA_HISTOGRAM_BOOLEAN("Blink.Canvas.HasRendered", bool(ResourceProvider())); |
| |
| // It's possible that the placeholder frame has been disposed but its ID still |
| // exists. Make sure that it gets unregistered here |
| UnregisterPlaceholderCanvas(); |
| |
| // We need to drop frame dispatcher, to prevent mojo calls from completing. |
| frame_dispatcher_ = nullptr; |
| DiscardResourceProvider(); |
| |
| if (context_) { |
| if (context_->Host()) |
| context_->DetachHost(); |
| context_ = nullptr; |
| } |
| |
| if (canvas2d_bridge_) { |
| canvas2d_bridge_->SetCanvasResourceHost(nullptr); |
| canvas2d_bridge_ = nullptr; |
| } |
| |
| if (surface_layer_bridge_) { |
| // Observer has to be cleared out at this point. Otherwise the |
| // SurfaceLayerBridge may call back into the observer which is undefined |
| // behavior. In the worst case, the dead canvas element re-adds itself into |
| // a data structure which may crash at a later point in time. See |
| // https://crbug.com/976577. |
| surface_layer_bridge_->ClearObserver(); |
| } |
| } |
| |
| void HTMLCanvasElement::ParseAttribute( |
| const AttributeModificationParams& params) { |
| if (params.name == html_names::kWidthAttr || |
| params.name == html_names::kHeightAttr) |
| Reset(); |
| HTMLElement::ParseAttribute(params); |
| } |
| |
| LayoutObject* HTMLCanvasElement::CreateLayoutObject(const ComputedStyle& style, |
| LegacyLayout legacy) { |
| if (GetExecutionContext() && |
| GetExecutionContext()->CanExecuteScripts(kNotAboutToExecuteScript)) { |
| // Allocation of a layout object indicates that the canvas doesn't |
| // have display:none set, so is conceptually being displayed. |
| if (context_) { |
| context_->SetIsBeingDisplayed(style.Visibility() == |
| EVisibility::kVisible); |
| } |
| return new LayoutHTMLCanvas(this); |
| } |
| return HTMLElement::CreateLayoutObject(style, legacy); |
| } |
| |
| Node::InsertionNotificationRequest HTMLCanvasElement::InsertedInto( |
| ContainerNode& node) { |
| SetIsInCanvasSubtree(true); |
| return HTMLElement::InsertedInto(node); |
| } |
| |
| void HTMLCanvasElement::setHeight(unsigned value, |
| ExceptionState& exception_state) { |
| if (IsOffscreenCanvasRegistered()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Cannot resize canvas after call to transferControlToOffscreen()."); |
| return; |
| } |
| SetUnsignedIntegralAttribute(html_names::kHeightAttr, value, |
| kDefaultCanvasHeight); |
| } |
| |
| void HTMLCanvasElement::setWidth(unsigned value, |
| ExceptionState& exception_state) { |
| if (IsOffscreenCanvasRegistered()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "Cannot resize canvas after call to transferControlToOffscreen()."); |
| return; |
| } |
| SetUnsignedIntegralAttribute(html_names::kWidthAttr, value, |
| kDefaultCanvasWidth); |
| } |
| |
| void HTMLCanvasElement::SetSize(const IntSize& new_size) { |
| if (new_size == Size()) |
| return; |
| ignore_reset_ = true; |
| SetIntegralAttribute(html_names::kWidthAttr, new_size.Width()); |
| SetIntegralAttribute(html_names::kHeightAttr, new_size.Height()); |
| ignore_reset_ = false; |
| Reset(); |
| } |
| |
| HTMLCanvasElement::ContextFactoryVector& |
| HTMLCanvasElement::RenderingContextFactories() { |
| DCHECK(IsMainThread()); |
| DEFINE_STATIC_LOCAL(ContextFactoryVector, context_factories, |
| (CanvasRenderingContext::kMaxValue)); |
| return context_factories; |
| } |
| |
| CanvasRenderingContextFactory* HTMLCanvasElement::GetRenderingContextFactory( |
| int type) { |
| DCHECK_LE(type, CanvasRenderingContext::kMaxValue); |
| return RenderingContextFactories()[type].get(); |
| } |
| |
| void HTMLCanvasElement::RegisterRenderingContextFactory( |
| std::unique_ptr<CanvasRenderingContextFactory> rendering_context_factory) { |
| CanvasRenderingContext::ContextType type = |
| rendering_context_factory->GetContextType(); |
| DCHECK_LE(type, CanvasRenderingContext::kMaxValue); |
| DCHECK(!RenderingContextFactories()[type]); |
| RenderingContextFactories()[type] = std::move(rendering_context_factory); |
| } |
| |
| void HTMLCanvasElement::RecordIdentifiabilityMetric( |
| IdentifiableSurface surface, |
| IdentifiableToken value) const { |
| blink::IdentifiabilityMetricBuilder(GetDocument().UkmSourceID()) |
| .Set(surface, value) |
| .Record(GetDocument().UkmRecorder()); |
| } |
| |
| void HTMLCanvasElement::IdentifiabilityReportWithDigest( |
| IdentifiableToken canvas_contents_token) const { |
| if (IdentifiabilityStudySettings::Get()->ShouldSample( |
| blink::IdentifiableSurface::Type::kCanvasReadback)) { |
| RecordIdentifiabilityMetric( |
| blink::IdentifiableSurface::FromTypeAndToken( |
| blink::IdentifiableSurface::Type::kCanvasReadback, |
| IdentifiabilityInputDigest(context_)), |
| canvas_contents_token.ToUkmMetricValue()); |
| } |
| } |
| |
| CanvasRenderingContext* HTMLCanvasElement::GetCanvasRenderingContext( |
| const String& type, |
| const CanvasContextCreationAttributesCore& attributes) { |
| auto* old_contents_cc_layer = ContentsCcLayer(); |
| auto* result = GetCanvasRenderingContextInternal(type, attributes); |
| |
| Document& doc = GetDocument(); |
| if (IdentifiabilityStudySettings::Get()->ShouldSample( |
| IdentifiableSurface::Type::kCanvasRenderingContext)) { |
| IdentifiabilityMetricBuilder(doc.UkmSourceID()) |
| .Set(IdentifiableSurface::FromTypeAndToken( |
| IdentifiableSurface::Type::kCanvasRenderingContext, |
| CanvasRenderingContext::ContextTypeFromId(type)), |
| !!result) |
| .Record(doc.UkmRecorder()); |
| } |
| if (attributes.color_space != kSRGBCanvasColorSpaceName || |
| attributes.pixel_format != kUint8CanvasPixelFormatName) { |
| UseCounter::Count(doc, WebFeature::kCanvasUseColorSpace); |
| } |
| |
| if (ContentsCcLayer() != old_contents_cc_layer) |
| OnContentsCcLayerChanged(); |
| return result; |
| } |
| |
| CanvasRenderingContext* HTMLCanvasElement::GetCanvasRenderingContextInternal( |
| const String& type, |
| const CanvasContextCreationAttributesCore& attributes) { |
| CanvasRenderingContext::ContextType context_type = |
| CanvasRenderingContext::ContextTypeFromId(type); |
| |
| // Unknown type. |
| if (context_type == CanvasRenderingContext::kContextTypeUnknown) { |
| return nullptr; |
| } |
| |
| // Log the aliased context type used. |
| if (!context_) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.ContextType", context_type); |
| } |
| |
| context_type = |
| CanvasRenderingContext::ResolveContextTypeAliases(context_type); |
| |
| CanvasRenderingContextFactory* factory = |
| GetRenderingContextFactory(context_type); |
| if (!factory) |
| return nullptr; |
| |
| // FIXME - The code depends on the context not going away once created, to |
| // prevent JS from seeing a dangling pointer. So for now we will disallow the |
| // context from being changed once it is created. |
| if (context_) { |
| if (context_->GetContextType() == context_type) |
| return context_.Get(); |
| |
| factory->OnError(this, |
| "Canvas has an existing context of a different type"); |
| return nullptr; |
| } |
| |
| // If this context is cross-origin, it should prefer to use the low-power GPU |
| LocalFrame* frame = GetDocument().GetFrame(); |
| CanvasContextCreationAttributesCore recomputed_attributes = attributes; |
| if (frame && frame->IsCrossOriginToMainFrame()) |
| recomputed_attributes.power_preference = "low-power"; |
| |
| context_ = factory->Create(this, recomputed_attributes); |
| if (!context_) |
| return nullptr; |
| |
| // Since the |context_| is created, free the transparent image, |
| // |transparent_image_| created for this canvas if it exists. |
| if (transparent_image_.get()) { |
| transparent_image_.reset(); |
| } |
| |
| context_creation_was_blocked_ = false; |
| |
| probe::DidCreateCanvasContext(&GetDocument()); |
| |
| if (Is3d()) |
| UpdateMemoryUsage(); |
| |
| LayoutObject* layout_object = GetLayoutObject(); |
| if (layout_object) { |
| const ComputedStyle* style = GetComputedStyle(); |
| if (style) { |
| context_->SetIsBeingDisplayed(style->Visibility() == |
| EVisibility::kVisible); |
| } |
| |
| if (IsRenderingContext2D() && !context_->CreationAttributes().alpha) { |
| // In the alpha false case, canvas is initially opaque, so we need to |
| // trigger an invalidation. |
| DidDraw(); |
| } |
| } |
| |
| if (context_->CreationAttributes().desynchronized) { |
| if (!CreateLayer()) |
| return nullptr; |
| SetNeedsUnbufferedInputEvents(true); |
| frame_dispatcher_ = std::make_unique<CanvasResourceDispatcher>( |
| nullptr, |
| GetPage() |
| ->GetPageScheduler() |
| ->GetAgentGroupScheduler() |
| .CompositorTaskRunner(), |
| surface_layer_bridge_->GetFrameSinkId().client_id(), |
| surface_layer_bridge_->GetFrameSinkId().sink_id(), |
| CanvasResourceDispatcher::kInvalidPlaceholderCanvasId, size_); |
| // We don't actually need the begin frame signal when in low latency mode, |
| // but we need to subscribe to it or else dispatching frames will not work. |
| frame_dispatcher_->SetNeedsBeginFrame(GetPage()->IsPageVisible()); |
| |
| UseCounter::Count(GetDocument(), WebFeature::kHTMLCanvasElementLowLatency); |
| } |
| |
| // A 2D context does not know before lazy creation whether or not it is |
| // direct composited. The Canvas2DLayerBridge will handle this |
| if (!IsRenderingContext2D()) |
| SetNeedsCompositingUpdate(); |
| |
| return context_.Get(); |
| } |
| |
| ScriptPromise HTMLCanvasElement::convertToBlob( |
| ScriptState* script_state, |
| const ImageEncodeOptions* options, |
| ExceptionState& exception_state) { |
| return CanvasRenderingContextHost::convertToBlob(script_state, options, |
| exception_state, context_); |
| } |
| |
| bool HTMLCanvasElement::ShouldBeDirectComposited() const { |
| return (context_ && context_->IsComposited()) || (!!surface_layer_bridge_); |
| } |
| |
| bool HTMLCanvasElement::IsAccelerated() const { |
| return context_ && context_->IsAccelerated(); |
| } |
| |
| Settings* HTMLCanvasElement::GetSettings() const { |
| auto* window = DynamicTo<LocalDOMWindow>(GetExecutionContext()); |
| if (window && window->GetFrame()) |
| return window->GetFrame()->GetSettings(); |
| return nullptr; |
| } |
| |
| bool HTMLCanvasElement::IsWebGL1Enabled() const { |
| Settings* settings = GetSettings(); |
| return settings && settings->GetWebGL1Enabled(); |
| } |
| |
| bool HTMLCanvasElement::IsWebGL2Enabled() const { |
| Settings* settings = GetSettings(); |
| return settings && settings->GetWebGL2Enabled(); |
| } |
| |
| bool HTMLCanvasElement::IsWebGLBlocked() const { |
| Document& document = GetDocument(); |
| bool blocked = false; |
| mojo::Remote<mojom::blink::GpuDataManager> gpu_data_manager; |
| Platform::Current()->GetBrowserInterfaceBroker()->GetInterface( |
| gpu_data_manager.BindNewPipeAndPassReceiver()); |
| gpu_data_manager->Are3DAPIsBlockedForUrl(document.Url(), &blocked); |
| return blocked; |
| } |
| |
| void HTMLCanvasElement::DidDraw(const FloatRect& rect) { |
| if (rect.IsEmpty()) |
| return; |
| if (GetLayoutObject() && GetLayoutObject()->PreviousVisibilityVisible() && |
| GetDocument().GetPage()) |
| GetDocument().GetPage()->Animator().SetHasCanvasInvalidation(); |
| canvas_is_clear_ = false; |
| if (GetLayoutObject() && !LowLatencyEnabled()) |
| GetLayoutObject()->SetShouldCheckForPaintInvalidation(); |
| if (IsRenderingContext2D() && context_->ShouldAntialias() && GetPage() && |
| GetPage()->DeviceScaleFactorDeprecated() > 1.0f) { |
| FloatRect inflated_rect = rect; |
| inflated_rect.Inflate(1); |
| dirty_rect_.Unite(inflated_rect); |
| } else { |
| dirty_rect_.Unite(rect); |
| } |
| if (IsRenderingContext2D() && canvas2d_bridge_) |
| canvas2d_bridge_->DidDraw(rect); |
| } |
| |
| void HTMLCanvasElement::DidDraw() { |
| DidDraw(FloatRect(0, 0, Size().Width(), Size().Height())); |
| } |
| |
| void HTMLCanvasElement::PreFinalizeFrame() { |
| RecordCanvasSizeToUMA(size_); |
| |
| // PreFinalizeFrame indicates the end of a script task that may have rendered |
| // into the canvas, now is a good time to unlock cache entries. |
| auto* resource_provider = ResourceProvider(); |
| if (resource_provider) |
| resource_provider->ReleaseLockedImages(); |
| |
| // Low-latency 2d canvases produce their frames after the resource gets single |
| // buffered. |
| if (LowLatencyEnabled() && !dirty_rect_.IsEmpty() && |
| GetOrCreateCanvasResourceProvider(RasterModeHint::kPreferGPU)) { |
| // TryEnableSingleBuffering() the first time we FinalizeFrame(). This is |
| // a nop if already single buffered or if single buffering is unsupported. |
| ResourceProvider()->TryEnableSingleBuffering(); |
| } |
| } |
| |
| void HTMLCanvasElement::PostFinalizeFrame() { |
| if (LowLatencyEnabled() && !dirty_rect_.IsEmpty() && |
| GetOrCreateCanvasResourceProvider(RasterModeHint::kPreferGPU)) { |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| const scoped_refptr<CanvasResource> canvas_resource = |
| ResourceProvider()->ProduceCanvasResource(); |
| const FloatRect src_rect(0, 0, Size().Width(), Size().Height()); |
| dirty_rect_.Intersect(src_rect); |
| const IntRect int_dirty = EnclosingIntRect(dirty_rect_); |
| const SkIRect damage_rect = SkIRect::MakeXYWH( |
| int_dirty.X(), int_dirty.Y(), int_dirty.Width(), int_dirty.Height()); |
| const bool needs_vertical_flip = !RenderingContext()->IsOriginTopLeft(); |
| frame_dispatcher_->DispatchFrame(std::move(canvas_resource), start_time, |
| damage_rect, needs_vertical_flip, |
| IsOpaque()); |
| dirty_rect_ = FloatRect(); |
| } |
| |
| // If the canvas is visible, notifying listeners is taken care of in |
| // DoDeferredPaintInvalidation(), which allows the frame to be grabbed prior |
| // to compositing, which is critically important because compositing may clear |
| // the canvas's image. (e.g. WebGL context with preserveDrawingBuffer=false). |
| // If the canvas is not visible, DoDeferredPaintInvalidation will not get |
| // called, so we need to take care of business here. |
| if (!did_notify_listeners_for_current_frame_) |
| NotifyListenersCanvasChanged(); |
| did_notify_listeners_for_current_frame_ = false; |
| } |
| |
| void HTMLCanvasElement::DisableAcceleration( |
| std::unique_ptr<Canvas2DLayerBridge> |
| unaccelerated_bridge_used_for_testing) { |
| // Create and configure an unaccelerated Canvas2DLayerBridge. |
| std::unique_ptr<Canvas2DLayerBridge> bridge; |
| if (unaccelerated_bridge_used_for_testing) |
| bridge = std::move(unaccelerated_bridge_used_for_testing); |
| else |
| bridge = Create2DLayerBridge(RasterMode::kCPU); |
| |
| if (bridge && canvas2d_bridge_) |
| ReplaceExisting2dLayerBridge(std::move(bridge)); |
| |
| // We must force a paint invalidation on the canvas even if it's |
| // content did not change because it layer was destroyed. |
| DidDraw(); |
| SetNeedsCompositingUpdate(); |
| } |
| |
| void HTMLCanvasElement::SetNeedsCompositingUpdate() { |
| Element::SetNeedsCompositingUpdate(); |
| } |
| |
| void HTMLCanvasElement::DoDeferredPaintInvalidation() { |
| DCHECK(!dirty_rect_.IsEmpty()); |
| if (LowLatencyEnabled()) { |
| // Low latency canvas handles dirty propagation in FinalizeFrame(); |
| return; |
| } |
| LayoutBox* layout_box = GetLayoutBox(); |
| |
| FloatRect content_rect; |
| if (layout_box) { |
| if (auto* replaced = DynamicTo<LayoutReplaced>(layout_box)) |
| content_rect = FloatRect(replaced->ReplacedContentRect()); |
| else |
| content_rect = FloatRect(layout_box->PhysicalContentBoxRect()); |
| } |
| |
| if (IsRenderingContext2D()) { |
| FloatRect src_rect(0, 0, Size().Width(), Size().Height()); |
| dirty_rect_.Intersect(src_rect); |
| |
| FloatRect invalidation_rect; |
| if (layout_box) { |
| FloatRect mapped_dirty_rect = |
| MapRect(dirty_rect_, src_rect, content_rect); |
| if (context_->IsComposited()) { |
| // Composited 2D canvases need the dirty rect to be expressed relative |
| // to the content box, as opposed to the layout box. |
| mapped_dirty_rect.MoveBy(-content_rect.Location()); |
| } |
| invalidation_rect = mapped_dirty_rect; |
| } else { |
| invalidation_rect = dirty_rect_; |
| } |
| |
| if (dirty_rect_.IsEmpty()) |
| return; |
| |
| if (canvas2d_bridge_) |
| canvas2d_bridge_->DoPaintInvalidation(invalidation_rect); |
| } |
| |
| if (context_ && HasImageBitmapContext() && context_->CcLayer()) |
| context_->CcLayer()->SetNeedsDisplay(); |
| |
| NotifyListenersCanvasChanged(); |
| did_notify_listeners_for_current_frame_ = true; |
| |
| // Propagate the |dirty_rect_| accumulated so far to the compositor |
| // before restarting with a blank dirty rect. |
| // Canvas content updates do not need to be propagated as |
| // paint invalidations if the canvas is composited separately, since |
| // the canvas contents are sent separately through a texture layer. |
| if (layout_box && (!context_ || !context_->IsComposited())) { |
| // If the content box is larger than |src_rect|, the canvas's image is |
| // being stretched, so we need to account for color bleeding caused by the |
| // interpolation filter. |
| FloatRect src_rect(0, 0, Size().Width(), Size().Height()); |
| if (content_rect.Width() > src_rect.Width() || |
| content_rect.Height() > src_rect.Height()) { |
| dirty_rect_.Inflate(0.5); |
| } |
| |
| dirty_rect_.Intersect(src_rect); |
| PhysicalRect mapped_dirty_rect( |
| EnclosingIntRect(MapRect(dirty_rect_, src_rect, content_rect))); |
| layout_box->InvalidatePaintRectangle(mapped_dirty_rect); |
| } |
| dirty_rect_ = FloatRect(); |
| |
| DCHECK(dirty_rect_.IsEmpty()); |
| } |
| |
| void HTMLCanvasElement::Reset() { |
| if (ignore_reset_) |
| return; |
| |
| dirty_rect_ = FloatRect(); |
| |
| bool had_resource_provider = HasResourceProvider(); |
| |
| unsigned w = 0; |
| AtomicString value = FastGetAttribute(html_names::kWidthAttr); |
| if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, w) || |
| w > 0x7fffffffu) { |
| w = kDefaultCanvasWidth; |
| } |
| |
| unsigned h = 0; |
| value = FastGetAttribute(html_names::kHeightAttr); |
| if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, h) || |
| h > 0x7fffffffu) { |
| h = kDefaultCanvasHeight; |
| } |
| |
| if (IsRenderingContext2D()) { |
| context_->Reset(); |
| origin_clean_ = true; |
| } |
| |
| IntSize old_size = Size(); |
| IntSize new_size(w, h); |
| |
| // If the size of an existing buffer matches, we can just clear it instead of |
| // reallocating. This optimization is only done for 2D canvases for now. |
| if (had_resource_provider && old_size == new_size && IsRenderingContext2D()) { |
| if (!canvas_is_clear_) { |
| canvas_is_clear_ = true; |
| if (canvas2d_bridge_) |
| canvas2d_bridge_->ClearFrame(); |
| context_->ClearRect(0, 0, width(), height()); |
| } |
| return; |
| } |
| |
| SetSurfaceSize(new_size); |
| |
| if (Is3d() && old_size != Size()) |
| context_->Reshape(width(), height()); |
| |
| if (LayoutObject* layout_object = GetLayoutObject()) { |
| if (layout_object->IsCanvas()) { |
| if (old_size != Size()) { |
| To<LayoutHTMLCanvas>(layout_object)->CanvasSizeChanged(); |
| if (GetDocument().GetSettings()->GetAcceleratedCompositingEnabled()) |
| GetLayoutBox()->ContentChanged(kCanvasChanged); |
| } |
| if (had_resource_provider) |
| layout_object->SetShouldDoFullPaintInvalidation(); |
| } |
| } |
| } |
| |
| bool HTMLCanvasElement::PaintsIntoCanvasBuffer() const { |
| if (OffscreenCanvasFrame()) |
| return false; |
| DCHECK(context_); |
| if (!context_->IsComposited()) |
| return true; |
| auto* settings = GetDocument().GetSettings(); |
| if (settings && settings->GetAcceleratedCompositingEnabled()) |
| return false; |
| |
| return true; |
| } |
| |
| void HTMLCanvasElement::NotifyListenersCanvasChanged() { |
| if (listeners_.size() == 0) |
| return; |
| |
| if (!OriginClean()) { |
| listeners_.clear(); |
| return; |
| } |
| |
| bool listener_needs_new_frame_capture = false; |
| for (const CanvasDrawListener* listener : listeners_) { |
| if (listener->NeedsNewFrame()) |
| listener_needs_new_frame_capture = true; |
| } |
| |
| if (listener_needs_new_frame_capture) { |
| SourceImageStatus status; |
| scoped_refptr<StaticBitmapImage> source_image = |
| GetSourceImageForCanvasInternal(&status); |
| if (status != kNormalSourceImageStatus) |
| return; |
| for (CanvasDrawListener* listener : listeners_) { |
| if (listener->NeedsNewFrame()) { |
| // Here we need to use the SharedGpuContext as some of the images may |
| // have been originated with other contextProvider, but we internally |
| // need a context_provider that has a RasterInterface available. |
| listener->SendNewFrame(source_image, |
| SharedGpuContext::ContextProviderWrapper()); |
| } |
| } |
| } |
| } |
| |
| // Returns an image and the image's resolution scale factor. |
| static std::pair<blink::Image*, float> BrokenCanvas(float device_scale_factor) { |
| if (device_scale_factor >= 2) { |
| DEFINE_STATIC_REF(blink::Image, broken_canvas_hi_res, |
| (blink::Image::LoadPlatformResource( |
| IDR_BROKENCANVAS, ui::SCALE_FACTOR_200P))); |
| return std::make_pair(broken_canvas_hi_res, 2); |
| } |
| |
| DEFINE_STATIC_REF(blink::Image, broken_canvas_lo_res, |
| (blink::Image::LoadPlatformResource(IDR_BROKENCANVAS))); |
| return std::make_pair(broken_canvas_lo_res, 1); |
| } |
| |
| static SkFilterQuality FilterQualityFromStyle(const ComputedStyle* style) { |
| if (style && style->ImageRendering() == EImageRendering::kPixelated) |
| return kNone_SkFilterQuality; |
| return kLow_SkFilterQuality; |
| } |
| |
| SkFilterQuality HTMLCanvasElement::FilterQuality() const { |
| if (!isConnected()) |
| return kLow_SkFilterQuality; |
| |
| const ComputedStyle* style = GetComputedStyle(); |
| if (!style) { |
| GetDocument().UpdateStyleAndLayoutTreeForNode(this); |
| HTMLCanvasElement* non_const_this = const_cast<HTMLCanvasElement*>(this); |
| style = non_const_this->EnsureComputedStyle(); |
| } |
| return FilterQualityFromStyle(style); |
| } |
| |
| bool HTMLCanvasElement::LowLatencyEnabled() const { |
| return !!frame_dispatcher_; |
| } |
| |
| void HTMLCanvasElement::UpdateFilterQuality(SkFilterQuality filter_quality) { |
| if (IsOffscreenCanvasRegistered()) |
| UpdateOffscreenCanvasFilterQuality(filter_quality); |
| |
| if (context_ && Is3d()) |
| context_->SetFilterQuality(filter_quality); |
| else if (canvas2d_bridge_) |
| canvas2d_bridge_->SetFilterQuality(filter_quality); |
| } |
| |
| // In some instances we don't actually want to paint to the parent layer |
| // We still might want to set filter quality and MarkFirstContentfulPaint though |
| void HTMLCanvasElement::Paint(GraphicsContext& context, |
| const PhysicalRect& r, |
| bool flatten_composited_layers) { |
| if (context_creation_was_blocked_ || |
| (context_ && context_->isContextLost())) { |
| float device_scale_factor = |
| blink::DeviceScaleFactorDeprecated(GetDocument().GetFrame()); |
| std::pair<Image*, float> broken_canvas_and_image_scale_factor = |
| BrokenCanvas(device_scale_factor); |
| Image* broken_canvas = broken_canvas_and_image_scale_factor.first; |
| context.Save(); |
| context.FillRect(FloatRect(r), Color(), SkBlendMode::kClear); |
| // Place the icon near the upper left, like the missing image icon |
| // for image elements. Offset it a bit from the upper corner. |
| FloatSize icon_size(broken_canvas->Size()); |
| FloatPoint upper_left = |
| FloatPoint(r.PixelSnappedOffset()) + icon_size.ScaledBy(0.5f); |
| context.DrawImage(broken_canvas, Image::kSyncDecode, |
| FloatRect(upper_left, icon_size)); |
| context.Restore(); |
| return; |
| } |
| |
| // FIXME: crbug.com/438240; there is a bug with the new CSS blending and |
| // compositing feature. |
| if (!context_ && !OffscreenCanvasFrame()) |
| return; |
| |
| // If the canvas is gpu composited, it has another way of getting to screen |
| if (!PaintsIntoCanvasBuffer()) { |
| // For click-and-drag or printing we still want to draw |
| if (!(flatten_composited_layers || GetDocument().Printing())) |
| return; |
| } |
| |
| if (OffscreenCanvasFrame()) { |
| DCHECK(GetDocument().Printing()); |
| scoped_refptr<StaticBitmapImage> image_for_printing = |
| OffscreenCanvasFrame()->Bitmap()->MakeUnaccelerated(); |
| context.DrawImage(image_for_printing.get(), Image::kSyncDecode, |
| FloatRect(PixelSnappedIntRect(r))); |
| return; |
| } |
| |
| PaintInternal(context, r); |
| } |
| |
| void HTMLCanvasElement::PaintInternal(GraphicsContext& context, |
| const PhysicalRect& r) { |
| context_->PaintRenderingResultsToCanvas(kFrontBuffer); |
| if (HasResourceProvider()) { |
| const ComputedStyle* style = GetComputedStyle(); |
| // For 2D Canvas, there are two ways of render Canvas for printing: |
| // display list or image snapshot. Display list allows better PDF printing |
| // and we prefer this method. |
| // Here are the requirements for display list to be used: |
| // 1. We must have had a full repaint of the Canvas after beginprint |
| // event has been fired. Otherwise, we don't have a PaintRecord. |
| // 2. CSS property 'image-rendering' must not be 'pixelated'. |
| |
| // display list rendering: we replay the last full PaintRecord, if Canvas |
| // has been redraw since beginprint happened. |
| if (IsPrinting() && !Is3d() && canvas2d_bridge_) { |
| canvas2d_bridge_->FlushRecording(); |
| if (canvas2d_bridge_->getLastRecord()) { |
| if (style && style->ImageRendering() != EImageRendering::kPixelated) { |
| context.Canvas()->save(); |
| context.Canvas()->translate(r.X(), r.Y()); |
| context.Canvas()->scale(r.Width() / Size().Width(), |
| r.Height() / Size().Height()); |
| context.Canvas()->drawPicture(canvas2d_bridge_->getLastRecord()); |
| context.Canvas()->restore(); |
| UMA_HISTOGRAM_BOOLEAN("Blink.Canvas.2DPrintingAsVector", true); |
| return; |
| } |
| } |
| UMA_HISTOGRAM_BOOLEAN("Blink.Canvas.2DPrintingAsVector", false); |
| } |
| // or image snapshot rendering: grab a snapshot and raster it. |
| SkBlendMode composite_operator = |
| !context_ || context_->CreationAttributes().alpha |
| ? SkBlendMode::kSrcOver |
| : SkBlendMode::kSrc; |
| FloatRect src_rect = FloatRect(FloatPoint(), FloatSize(Size())); |
| scoped_refptr<StaticBitmapImage> snapshot = |
| canvas2d_bridge_ |
| ? canvas2d_bridge_->NewImageSnapshot() |
| : (ResourceProvider() ? ResourceProvider()->Snapshot() : nullptr); |
| if (snapshot) { |
| // GraphicsContext cannot handle gpu resource serialization. |
| snapshot = snapshot->MakeUnaccelerated(); |
| DCHECK(!snapshot->IsTextureBacked()); |
| context.DrawImage(snapshot.get(), Image::kSyncDecode, |
| FloatRect(PixelSnappedIntRect(r)), &src_rect, |
| style && style->HasFilterInducingProperty(), |
| composite_operator); |
| } |
| } else { |
| // When alpha is false, we should draw to opaque black. |
| if (!context_->CreationAttributes().alpha) |
| context.FillRect(FloatRect(r), Color(0, 0, 0)); |
| } |
| |
| if (Is3d() && PaintsIntoCanvasBuffer()) |
| context_->MarkLayerComposited(); |
| } |
| |
| bool HTMLCanvasElement::IsPrinting() const { |
| return GetDocument().BeforePrintingOrPrinting(); |
| } |
| |
| UkmParameters HTMLCanvasElement::GetUkmParameters() { |
| return {GetDocument().UkmRecorder(), GetDocument().UkmSourceID()}; |
| } |
| |
| void HTMLCanvasElement::SetSurfaceSize(const IntSize& size) { |
| size_ = size; |
| did_fail_to_create_resource_provider_ = false; |
| DiscardResourceProvider(); |
| if (IsRenderingContext2D() && context_->isContextLost()) |
| context_->DidSetSurfaceSize(); |
| if (frame_dispatcher_) |
| frame_dispatcher_->Reshape(size_); |
| } |
| |
| const AtomicString HTMLCanvasElement::ImageSourceURL() const { |
| return AtomicString(ToDataURLInternal( |
| ImageEncoderUtils::kDefaultRequestedMimeType, 0, kFrontBuffer)); |
| } |
| |
| scoped_refptr<StaticBitmapImage> HTMLCanvasElement::Snapshot( |
| SourceDrawingBuffer source_buffer) const { |
| if (size_.IsEmpty()) |
| return nullptr; |
| |
| scoped_refptr<StaticBitmapImage> image_bitmap = nullptr; |
| if (OffscreenCanvasFrame()) { // Offscreen Canvas |
| DCHECK(OffscreenCanvasFrame()->OriginClean()); |
| image_bitmap = OffscreenCanvasFrame()->Bitmap(); |
| } else if (Is3d()) { // WebGL or WebGL2 canvas |
| if (context_->CreationAttributes().premultiplied_alpha) { |
| context_->PaintRenderingResultsToCanvas(source_buffer); |
| if (ResourceProvider()) |
| image_bitmap = ResourceProvider()->Snapshot(); |
| } else { |
| sk_sp<SkData> pixel_data = |
| context_->PaintRenderingResultsToDataArray(source_buffer); |
| if (pixel_data) { |
| // If the accelerated canvas is too big, there is a logic in WebGL code |
| // path that scales down the drawing buffer to the maximum supported |
| // size. Hence, we need to query the adjusted size of DrawingBuffer. |
| IntSize adjusted_size = context_->DrawingBufferSize(); |
| SkImageInfo info = |
| SkImageInfo::Make(adjusted_size.Width(), adjusted_size.Height(), |
| kRGBA_8888_SkColorType, kUnpremul_SkAlphaType); |
| info = info.makeColorSpace(ColorParams().GetSkColorSpace()); |
| if (ColorParams().GetSkColorType() != kN32_SkColorType) |
| info = info.makeColorType(kRGBA_F16_SkColorType); |
| image_bitmap = StaticBitmapImage::Create(std::move(pixel_data), info); |
| } |
| } |
| } else if (canvas2d_bridge_) { |
| DCHECK(IsRenderingContext2D()); |
| image_bitmap = canvas2d_bridge_->NewImageSnapshot(); |
| } else if (context_) { // Bitmap renderer canvas |
| image_bitmap = context_->GetImage(); |
| } |
| |
| if (!image_bitmap) |
| image_bitmap = CreateTransparentImage(size_); |
| return image_bitmap; |
| } |
| |
| String HTMLCanvasElement::ToDataURLInternal( |
| const String& mime_type, |
| const double& quality, |
| SourceDrawingBuffer source_buffer) const { |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| if (!IsPaintable()) |
| return String("data:,"); |
| |
| ImageEncodingMimeType encoding_mime_type = |
| ImageEncoderUtils::ToEncodingMimeType( |
| mime_type, ImageEncoderUtils::kEncodeReasonToDataURL); |
| |
| scoped_refptr<StaticBitmapImage> image_bitmap = Snapshot(source_buffer); |
| if (image_bitmap) { |
| std::unique_ptr<ImageDataBuffer> data_buffer = |
| ImageDataBuffer::Create(image_bitmap); |
| if (!data_buffer) |
| return String("data:,"); |
| |
| String data_url = data_buffer->ToDataURL(encoding_mime_type, quality); |
| base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time; |
| float sqrt_pixels = |
| std::sqrt(image_bitmap->width()) * std::sqrt(image_bitmap->height()); |
| float scaled_time_float = elapsed_time.InMicrosecondsF() / |
| (sqrt_pixels == 0 ? 1.0f : sqrt_pixels); |
| |
| // If scaled_time_float overflows as integer, CheckedNumeric will store it |
| // as invalid, then ValueOrDefault will return the maximum int. |
| base::CheckedNumeric<int> checked_scaled_time = scaled_time_float; |
| int scaled_time_int = |
| checked_scaled_time.ValueOrDefault(std::numeric_limits<int>::max()); |
| |
| if (encoding_mime_type == kMimeTypePng) { |
| UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToDataURLScaledDuration.PNG", |
| scaled_time_int); |
| } else if (encoding_mime_type == kMimeTypeJpeg) { |
| UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToDataURLScaledDuration.JPEG", |
| scaled_time_int); |
| } else if (encoding_mime_type == kMimeTypeWebp) { |
| UMA_HISTOGRAM_COUNTS_100000("Blink.Canvas.ToDataURLScaledDuration.WEBP", |
| scaled_time_int); |
| } else { |
| // Currently we only support three encoding types. |
| NOTREACHED(); |
| } |
| IdentifiabilityReportWithDigest(IdentifiabilityBenignStringToken(data_url)); |
| return data_url; |
| } |
| |
| return String("data:,"); |
| } |
| |
| String HTMLCanvasElement::toDataURL(const String& mime_type, |
| const ScriptValue& quality_argument, |
| ExceptionState& exception_state) const { |
| if (!OriginClean()) { |
| exception_state.ThrowSecurityError("Tainted canvases may not be exported."); |
| return String(); |
| } |
| |
| double quality = kUndefinedQualityValue; |
| if (!quality_argument.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = quality_argument.V8Value(); |
| if (v8_value->IsNumber()) |
| quality = v8_value.As<v8::Number>()->Value(); |
| } |
| return ToDataURLInternal(mime_type, quality, kBackBuffer); |
| } |
| |
| void HTMLCanvasElement::toBlob(V8BlobCallback* callback, |
| const String& mime_type, |
| const ScriptValue& quality_argument, |
| ExceptionState& exception_state) { |
| if (!OriginClean()) { |
| exception_state.ThrowSecurityError("Tainted canvases may not be exported."); |
| return; |
| } |
| |
| if (!GetExecutionContext()) |
| return; |
| |
| if (!IsPaintable()) { |
| // If the canvas element's bitmap has no pixels |
| GetDocument() |
| .GetTaskRunner(TaskType::kCanvasBlobSerialization) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&V8BlobCallback::InvokeAndReportException, |
| WrapPersistent(callback), nullptr, nullptr)); |
| return; |
| } |
| |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| double quality = kUndefinedQualityValue; |
| if (!quality_argument.IsEmpty()) { |
| v8::Local<v8::Value> v8_value = quality_argument.V8Value(); |
| if (v8_value->IsNumber()) |
| quality = v8_value.As<v8::Number>()->Value(); |
| } |
| |
| ImageEncodingMimeType encoding_mime_type = |
| ImageEncoderUtils::ToEncodingMimeType( |
| mime_type, ImageEncoderUtils::kEncodeReasonToBlobCallback); |
| |
| CanvasAsyncBlobCreator* async_creator = nullptr; |
| scoped_refptr<StaticBitmapImage> image_bitmap = Snapshot(kBackBuffer); |
| if (image_bitmap) { |
| auto* options = ImageEncodeOptions::Create(); |
| options->setType(ImageEncodingMimeTypeName(encoding_mime_type)); |
| async_creator = MakeGarbageCollected<CanvasAsyncBlobCreator>( |
| image_bitmap, options, |
| CanvasAsyncBlobCreator::kHTMLCanvasToBlobCallback, callback, start_time, |
| GetExecutionContext(), |
| IdentifiabilityStudySettings::Get()->IsTypeAllowed( |
| IdentifiableSurface::Type::kCanvasReadback) |
| ? IdentifiabilityInputDigest(context_) |
| : 0); |
| } |
| |
| if (async_creator) { |
| async_creator->ScheduleAsyncBlobCreation(quality); |
| } else { |
| GetDocument() |
| .GetTaskRunner(TaskType::kCanvasBlobSerialization) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&V8BlobCallback::InvokeAndReportException, |
| WrapPersistent(callback), nullptr, nullptr)); |
| } |
| } |
| |
| bool HTMLCanvasElement::IsPresentationAttribute( |
| const QualifiedName& name) const { |
| if (name == html_names::kWidthAttr || name == html_names::kHeightAttr) |
| return true; |
| return HTMLElement::IsPresentationAttribute(name); |
| } |
| |
| void HTMLCanvasElement::CollectStyleForPresentationAttribute( |
| const QualifiedName& name, |
| const AtomicString& value, |
| MutableCSSPropertyValueSet* style) { |
| if (name == html_names::kWidthAttr) { |
| if (FastHasAttribute(html_names::kHeightAttr)) { |
| const AtomicString& height = FastGetAttribute(html_names::kHeightAttr); |
| ApplyAspectRatioToStyle(value, height, style); |
| } |
| } else if (name == html_names::kHeightAttr) { |
| if (FastHasAttribute(html_names::kWidthAttr)) { |
| const AtomicString& width = FastGetAttribute(html_names::kWidthAttr); |
| ApplyAspectRatioToStyle(width, value, style); |
| } |
| } else { |
| HTMLElement::CollectStyleForPresentationAttribute(name, value, style); |
| } |
| } |
| |
| void HTMLCanvasElement::AddListener(CanvasDrawListener* listener) { |
| listeners_.insert(listener); |
| } |
| |
| void HTMLCanvasElement::RemoveListener(CanvasDrawListener* listener) { |
| listeners_.erase(listener); |
| } |
| |
| bool HTMLCanvasElement::OriginClean() const { |
| if (GetDocument().GetSettings() && |
| GetDocument().GetSettings()->GetDisableReadingFromCanvas()) { |
| return false; |
| } |
| if (OffscreenCanvasFrame()) |
| return OffscreenCanvasFrame()->OriginClean(); |
| return origin_clean_; |
| } |
| |
| bool HTMLCanvasElement::ShouldAccelerate2dContext() const { |
| return ShouldAccelerate(); |
| } |
| |
| CanvasResourceDispatcher* HTMLCanvasElement::GetOrCreateResourceDispatcher() { |
| // The HTMLCanvasElement override of this method never needs to 'create' |
| // because the frame_dispatcher is only used in low latency mode, in which |
| // case the dispatcher is created upfront. |
| return frame_dispatcher_.get(); |
| } |
| |
| bool HTMLCanvasElement::PushFrame(scoped_refptr<CanvasResource> image, |
| const SkIRect& damage_rect) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| bool HTMLCanvasElement::ShouldAccelerate() const { |
| if (context_ && !IsRenderingContext2D()) |
| return false; |
| |
| // The command line flag --disable-accelerated-2d-canvas toggles this option |
| if (!RuntimeEnabledFeatures::Accelerated2dCanvasEnabled()) |
| return false; |
| |
| // Webview crashes with accelerated small canvases (crbug.com/1004304) |
| // Experimenting to see if this still causes crashes (crbug.com/1136603) |
| if (!RuntimeEnabledFeatures::AcceleratedSmallCanvasesEnabled() && |
| !base::FeatureList::IsEnabled( |
| features::kWebviewAccelerateSmallCanvases)) { |
| base::CheckedNumeric<int> checked_canvas_pixel_count = |
| Size().Width() * Size().Height(); |
| if (!checked_canvas_pixel_count.IsValid()) |
| return false; |
| int canvas_pixel_count = checked_canvas_pixel_count.ValueOrDie(); |
| |
| if (canvas_pixel_count < kMinimumAccelerated2dCanvasSize) |
| return false; |
| } |
| |
| // The following is necessary for handling the special case of canvases in |
| // the dev tools overlay, which run in a process that supports accelerated |
| // 2d canvas but in a special compositing context that does not. |
| auto* settings = GetDocument().GetSettings(); |
| if (settings && !settings->GetAcceleratedCompositingEnabled()) |
| return false; |
| |
| // Avoid creating |contextProvider| until we're sure we want to try use it, |
| // since it costs us GPU memory. |
| base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper = |
| SharedGpuContext::ContextProviderWrapper(); |
| if (!context_provider_wrapper) |
| return false; |
| |
| return context_provider_wrapper->Utils()->Accelerated2DCanvasFeatureEnabled(); |
| } |
| |
| std::unique_ptr<Canvas2DLayerBridge> HTMLCanvasElement::Create2DLayerBridge( |
| RasterMode raster_mode) { |
| auto surface = |
| std::make_unique<Canvas2DLayerBridge>(Size(), raster_mode, ColorParams()); |
| if (!surface->IsValid()) |
| return nullptr; |
| |
| return surface; |
| } |
| |
| void HTMLCanvasElement::SetCanvas2DLayerBridgeInternal( |
| std::unique_ptr<Canvas2DLayerBridge> external_canvas2d_bridge) { |
| DCHECK(IsRenderingContext2D() && !canvas2d_bridge_); |
| did_fail_to_create_resource_provider_ = true; |
| |
| if (!IsValidImageSize(Size())) |
| return; |
| |
| if (external_canvas2d_bridge) { |
| if (external_canvas2d_bridge->IsValid()) |
| canvas2d_bridge_ = std::move(external_canvas2d_bridge); |
| } else { |
| // If the canvas meets the criteria to use accelerated-GPU rendering, and |
| // the user signals that the canvas will not be read frequently through |
| // getImageData, which is a slow operation with GPU, the canvas will try to |
| // use accelerated-GPU rendering. |
| // If any of the two conditions fails, or if the creation of accelerated |
| // resource provider fails, the canvas will fallback to CPU rendering. |
| UMA_HISTOGRAM_BOOLEAN("Blink.Canvas.WillReadFrequently", |
| context_->CreationAttributes().will_read_frequently); |
| |
| if (ShouldAccelerate() && |
| !context_->CreationAttributes().will_read_frequently) { |
| canvas2d_bridge_ = Create2DLayerBridge(RasterMode::kGPU); |
| } |
| if (!canvas2d_bridge_) { |
| canvas2d_bridge_ = Create2DLayerBridge(RasterMode::kCPU); |
| } |
| } |
| |
| if (!canvas2d_bridge_) |
| return; |
| |
| canvas2d_bridge_->SetCanvasResourceHost(this); |
| bool is_being_displayed = |
| GetLayoutObject() && GetComputedStyle() && |
| GetComputedStyle()->Visibility() == EVisibility::kVisible; |
| canvas2d_bridge_->SetIsBeingDisplayed(is_being_displayed); |
| |
| did_fail_to_create_resource_provider_ = false; |
| UpdateMemoryUsage(); |
| |
| if (context_) |
| SetNeedsCompositingUpdate(); |
| } |
| |
| void HTMLCanvasElement::NotifyGpuContextLost() { |
| if (IsRenderingContext2D()) |
| context_->LoseContext(CanvasRenderingContext::kRealLostContext); |
| } |
| |
| void HTMLCanvasElement::Trace(Visitor* visitor) const { |
| visitor->Trace(listeners_); |
| visitor->Trace(context_); |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| PageVisibilityObserver::Trace(visitor); |
| HTMLElement::Trace(visitor); |
| } |
| |
| Canvas2DLayerBridge* HTMLCanvasElement::GetOrCreateCanvas2DLayerBridge() { |
| DCHECK(IsRenderingContext2D()); |
| if (!canvas2d_bridge_ && !did_fail_to_create_resource_provider_) { |
| SetCanvas2DLayerBridgeInternal(nullptr); |
| if (did_fail_to_create_resource_provider_ && !Size().IsEmpty()) |
| context_->LoseContext(CanvasRenderingContext::kSyntheticLostContext); |
| } |
| return canvas2d_bridge_.get(); |
| } |
| |
| void HTMLCanvasElement::SetResourceProviderForTesting( |
| std::unique_ptr<CanvasResourceProvider> resource_provider, |
| std::unique_ptr<Canvas2DLayerBridge> bridge, |
| const IntSize& size) { |
| DiscardResourceProvider(); |
| SetIntegralAttribute(html_names::kWidthAttr, size.Width()); |
| SetIntegralAttribute(html_names::kHeightAttr, size.Height()); |
| SetCanvas2DLayerBridgeInternal(std::move(bridge)); |
| ReplaceResourceProvider(std::move(resource_provider)); |
| } |
| |
| void HTMLCanvasElement::DiscardResourceProvider() { |
| canvas2d_bridge_.reset(); |
| CanvasResourceHost::DiscardResourceProvider(); |
| dirty_rect_ = FloatRect(); |
| } |
| |
| void HTMLCanvasElement::PageVisibilityChanged() { |
| bool hidden = !GetPage()->IsPageVisible(); |
| SetSuspendOffscreenCanvasAnimation(hidden); |
| |
| if (!context_) |
| return; |
| |
| context_->SetIsInHiddenPage(hidden); |
| if (hidden && Is3d()) |
| DiscardResourceProvider(); |
| } |
| |
| void HTMLCanvasElement::ContextDestroyed() { |
| if (context_) |
| context_->Stop(); |
| } |
| |
| bool HTMLCanvasElement::StyleChangeNeedsDidDraw( |
| const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| // It will only need to redraw for a style change, if the new imageRendering |
| // is different than the previous one, and only if one of the two ir |
| // pixelated. |
| return old_style && |
| old_style->ImageRendering() != new_style.ImageRendering() && |
| (old_style->ImageRendering() == EImageRendering::kPixelated || |
| new_style.ImageRendering() == EImageRendering::kPixelated); |
| } |
| |
| void HTMLCanvasElement::StyleDidChange(const ComputedStyle* old_style, |
| const ComputedStyle& new_style) { |
| UpdateFilterQuality(FilterQualityFromStyle(&new_style)); |
| if (context_) |
| context_->StyleDidChange(old_style, new_style); |
| if (StyleChangeNeedsDidDraw(old_style, new_style)) |
| DidDraw(); |
| } |
| |
| void HTMLCanvasElement::LayoutObjectDestroyed() { |
| // If the canvas has no layout object then it definitely isn't being |
| // displayed any more. |
| if (context_) |
| context_->SetIsBeingDisplayed(false); |
| } |
| |
| void HTMLCanvasElement::DidMoveToNewDocument(Document& old_document) { |
| SetExecutionContext(GetExecutionContext()); |
| SetPage(GetDocument().GetPage()); |
| HTMLElement::DidMoveToNewDocument(old_document); |
| } |
| |
| void HTMLCanvasElement::WillDrawImageTo2DContext(CanvasImageSource* source) { |
| if (SharedGpuContext::AllowSoftwareToAcceleratedCanvasUpgrade() && |
| source->IsAccelerated() && GetOrCreateCanvas2DLayerBridge() && |
| !canvas2d_bridge_->IsAccelerated() && ShouldAccelerate()) { |
| std::unique_ptr<Canvas2DLayerBridge> surface = |
| Create2DLayerBridge(RasterMode::kGPU); |
| if (surface) { |
| ReplaceExisting2dLayerBridge(std::move(surface)); |
| SetNeedsCompositingUpdate(); |
| } |
| } |
| } |
| |
| scoped_refptr<Image> HTMLCanvasElement::GetSourceImageForCanvas( |
| SourceImageStatus* status, |
| const FloatSize&) { |
| return GetSourceImageForCanvasInternal(status); |
| } |
| |
| scoped_refptr<StaticBitmapImage> |
| HTMLCanvasElement::GetSourceImageForCanvasInternal(SourceImageStatus* status) { |
| if (!width() || !height()) { |
| *status = kZeroSizeCanvasSourceImageStatus; |
| return nullptr; |
| } |
| |
| if (!IsPaintable()) { |
| *status = kInvalidSourceImageStatus; |
| return nullptr; |
| } |
| |
| if (OffscreenCanvasFrame()) { |
| *status = kNormalSourceImageStatus; |
| return OffscreenCanvasFrame()->Bitmap(); |
| } |
| |
| if (!context_) { |
| scoped_refptr<StaticBitmapImage> result = GetTransparentImage(); |
| *status = result ? kNormalSourceImageStatus : kInvalidSourceImageStatus; |
| return result; |
| } |
| |
| if (HasImageBitmapContext()) { |
| *status = kNormalSourceImageStatus; |
| scoped_refptr<StaticBitmapImage> result = context_->GetImage(); |
| if (!result) |
| result = GetTransparentImage(); |
| *status = result ? kNormalSourceImageStatus : kInvalidSourceImageStatus; |
| return result; |
| } |
| |
| scoped_refptr<StaticBitmapImage> image; |
| // TODO(ccameron): Canvas should produce sRGB images. |
| // https://crbug.com/672299 |
| if (Is3d()) { |
| // Because WebGL sources always require making a copy of the back buffer, we |
| // use paintRenderingResultsToCanvas instead of getImage in order to keep a |
| // cached copy of the backing in the canvas's resource provider. |
| RenderingContext()->PaintRenderingResultsToCanvas(kBackBuffer); |
| if (ResourceProvider()) |
| image = ResourceProvider()->Snapshot(); |
| else |
| image = GetTransparentImage(); |
| } else { |
| image = RenderingContext()->GetImage(); |
| if (!image) |
| image = GetTransparentImage(); |
| } |
| |
| if (image) |
| *status = kNormalSourceImageStatus; |
| else |
| *status = kInvalidSourceImageStatus; |
| return image; |
| } |
| |
| bool HTMLCanvasElement::WouldTaintOrigin() const { |
| return !OriginClean(); |
| } |
| |
| FloatSize HTMLCanvasElement::ElementSize( |
| const FloatSize&, |
| const RespectImageOrientationEnum) const { |
| if (context_ && HasImageBitmapContext()) { |
| scoped_refptr<Image> image = context_->GetImage(); |
| if (image) |
| return FloatSize(image->width(), image->height()); |
| return FloatSize(0, 0); |
| } |
| if (OffscreenCanvasFrame()) |
| return FloatSize(OffscreenCanvasFrame()->Size()); |
| return FloatSize(width(), height()); |
| } |
| |
| IntSize HTMLCanvasElement::BitmapSourceSize() const { |
| return IntSize(width(), height()); |
| } |
| |
| ScriptPromise HTMLCanvasElement::CreateImageBitmap( |
| ScriptState* script_state, |
| base::Optional<IntRect> crop_rect, |
| const ImageBitmapOptions* options, |
| ExceptionState& exception_state) { |
| return ImageBitmapSource::FulfillImageBitmap( |
| script_state, MakeGarbageCollected<ImageBitmap>(this, crop_rect, options), |
| exception_state); |
| } |
| |
| void HTMLCanvasElement::SetOffscreenCanvasResource( |
| scoped_refptr<CanvasResource> image, |
| viz::ResourceId resource_id) { |
| OffscreenCanvasPlaceholder::SetOffscreenCanvasResource(std::move(image), |
| resource_id); |
| SetSize(OffscreenCanvasFrame()->Size()); |
| NotifyListenersCanvasChanged(); |
| } |
| |
| bool HTMLCanvasElement::IsOpaque() const { |
| return context_ && !context_->CreationAttributes().alpha; |
| } |
| |
| bool HTMLCanvasElement::IsSupportedInteractiveCanvasFallback( |
| const Element& element) { |
| if (!element.IsDescendantOf(this)) |
| return false; |
| |
| // An element is a supported interactive canvas fallback element if it is one |
| // of the following: |
| // https://html.spec.whatwg.org/C/#supported-interactive-canvas-fallback-element |
| |
| // An a element that represents a hyperlink and that does not have any img |
| // descendants. |
| if (IsA<HTMLAnchorElement>(element)) |
| return !Traversal<HTMLImageElement>::FirstWithin(element); |
| |
| // A button element |
| if (IsA<HTMLButtonElement>(element)) |
| return true; |
| |
| // An input element whose type attribute is in one of the Checkbox or Radio |
| // Button states. An input element that is a button but its type attribute is |
| // not in the Image Button state. |
| if (auto* input_element = DynamicTo<HTMLInputElement>(element)) { |
| if (input_element->type() == input_type_names::kCheckbox || |
| input_element->type() == input_type_names::kRadio || |
| input_element->IsTextButton()) { |
| return true; |
| } |
| } |
| |
| // A select element with a "multiple" attribute or with a display size greater |
| // than 1. |
| if (auto* select_element = DynamicTo<HTMLSelectElement>(element)) { |
| if (select_element->IsMultiple() || select_element->size() > 1) |
| return true; |
| } |
| |
| // An option element that is in a list of options of a select element with a |
| // "multiple" attribute or with a display size greater than 1. |
| const auto* parent_select = |
| IsA<HTMLOptionElement>(element) |
| ? DynamicTo<HTMLSelectElement>(element.parentNode()) |
| : nullptr; |
| |
| if (parent_select && |
| (parent_select->IsMultiple() || parent_select->size() > 1)) |
| return true; |
| |
| // An element that would not be interactive content except for having the |
| // tabindex attribute specified. |
| if (element.FastHasAttribute(html_names::kTabindexAttr)) |
| return true; |
| |
| // A non-interactive table, caption, thead, tbody, tfoot, tr, td, or th |
| // element. |
| if (IsA<HTMLTableElement>(element) || |
| element.HasTagName(html_names::kCaptionTag) || |
| element.HasTagName(html_names::kTheadTag) || |
| element.HasTagName(html_names::kTbodyTag) || |
| element.HasTagName(html_names::kTfootTag) || |
| element.HasTagName(html_names::kTrTag) || |
| element.HasTagName(html_names::kTdTag) || |
| element.HasTagName(html_names::kThTag)) |
| return true; |
| |
| return false; |
| } |
| |
| HitTestCanvasResult* HTMLCanvasElement::GetControlAndIdIfHitRegionExists( |
| const PhysicalOffset& location) { |
| if (IsRenderingContext2D()) |
| return context_->GetControlAndIdIfHitRegionExists(location); |
| return MakeGarbageCollected<HitTestCanvasResult>(String(), nullptr); |
| } |
| |
| String HTMLCanvasElement::GetIdFromControl(const Element* element) { |
| if (context_) |
| return context_->GetIdFromControl(element); |
| return String(); |
| } |
| |
| bool HTMLCanvasElement::CreateLayer() { |
| DCHECK(!surface_layer_bridge_); |
| LocalFrame* frame = GetDocument().GetFrame(); |
| // We do not design transferControlToOffscreen() for frame-less HTML canvas. |
| if (!frame) |
| return false; |
| |
| surface_layer_bridge_ = std::make_unique<::blink::SurfaceLayerBridge>( |
| frame->GetPage()->GetChromeClient().GetFrameSinkId(frame), |
| ::blink::SurfaceLayerBridge::ContainsVideo::kNo, this, |
| base::NullCallback()); |
| // Creates a placeholder layer first before Surface is created. |
| surface_layer_bridge_->CreateSolidColorLayer(); |
| // This may cause the canvas to be composited. |
| SetNeedsCompositingUpdate(); |
| |
| return true; |
| } |
| |
| void HTMLCanvasElement::OnWebLayerUpdated() { |
| SetNeedsCompositingUpdate(); |
| } |
| |
| void HTMLCanvasElement::RegisterContentsLayer(cc::Layer* layer) { |
| OnContentsCcLayerChanged(); |
| } |
| |
| void HTMLCanvasElement::UnregisterContentsLayer(cc::Layer* layer) { |
| OnContentsCcLayerChanged(); |
| } |
| |
| FontSelector* HTMLCanvasElement::GetFontSelector() { |
| return GetDocument().GetStyleEngine().GetFontSelector(); |
| } |
| |
| void HTMLCanvasElement::UpdateMemoryUsage() { |
| int non_gpu_buffer_count = 0; |
| int gpu_buffer_count = 0; |
| |
| if (!IsRenderingContext2D() && !Is3d()) |
| return; |
| if (ResourceProvider()) { |
| non_gpu_buffer_count++; |
| if (IsAccelerated()) { |
| // The number of internal GPU buffers vary between one (stable |
| // non-displayed state) and three (triple-buffered animations). |
| // Adding 2 is a pessimistic but relevant estimate. |
| // Note: These buffers might be allocated in GPU memory. |
| gpu_buffer_count += 2; |
| } |
| } |
| |
| if (Is3d()) |
| non_gpu_buffer_count += context_->ExternallyAllocatedBufferCountPerPixel(); |
| |
| const int bytes_per_pixel = ColorParams().BytesPerPixel(); |
| |
| intptr_t gpu_memory_usage = 0; |
| uint32_t canvas_width = std::min(kMaximumCanvasSize, width()); |
| uint32_t canvas_height = std::min(kMaximumCanvasSize, height()); |
| |
| if (gpu_buffer_count) { |
| // Switch from cpu mode to gpu mode |
| base::CheckedNumeric<intptr_t> checked_usage = |
| gpu_buffer_count * bytes_per_pixel; |
| checked_usage *= canvas_width; |
| checked_usage *= canvas_height; |
| gpu_memory_usage = |
| checked_usage.ValueOrDefault(std::numeric_limits<intptr_t>::max()); |
| } |
| |
| // Recomputation of externally memory usage computation is carried out |
| // in all cases. |
| base::CheckedNumeric<intptr_t> checked_usage = |
| non_gpu_buffer_count * bytes_per_pixel; |
| checked_usage *= canvas_width; |
| checked_usage *= canvas_height; |
| checked_usage += gpu_memory_usage; |
| intptr_t externally_allocated_memory = |
| checked_usage.ValueOrDefault(std::numeric_limits<intptr_t>::max()); |
| // Subtracting two intptr_t that are known to be positive will never |
| // underflow. |
| v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory( |
| externally_allocated_memory - externally_allocated_memory_); |
| externally_allocated_memory_ = externally_allocated_memory; |
| } |
| |
| void HTMLCanvasElement::ReplaceExisting2dLayerBridge( |
| std::unique_ptr<Canvas2DLayerBridge> new_layer_bridge) { |
| scoped_refptr<StaticBitmapImage> image; |
| std::unique_ptr<Canvas2DLayerBridge> old_layer_bridge; |
| if (canvas2d_bridge_) { |
| image = canvas2d_bridge_->NewImageSnapshot(); |
| // image can be null if allocation failed in which case we should just |
| // abort the surface switch to retain the old surface which is still |
| // functional. |
| if (!image) |
| return; |
| old_layer_bridge = std::move(canvas2d_bridge_); |
| // Removing connection between old_layer_bridge and CanvasResourceHost; |
| // otherwise, the CanvasResourceHost checks for the old one before |
| // assigning the new canvas layer bridge. |
| old_layer_bridge->SetCanvasResourceHost(nullptr); |
| } |
| ReplaceResourceProvider(nullptr); |
| canvas2d_bridge_ = std::move(new_layer_bridge); |
| canvas2d_bridge_->SetCanvasResourceHost(this); |
| |
| // If PaintCanvas cannot be get from the new layer bridge, revert the |
| // replacement. |
| cc::PaintCanvas* canvas = canvas2d_bridge_->GetPaintCanvas(); |
| if (!canvas) { |
| if (old_layer_bridge) { |
| canvas2d_bridge_ = std::move(old_layer_bridge); |
| canvas2d_bridge_->SetCanvasResourceHost(this); |
| } |
| return; |
| } |
| |
| // After validating paint canvas on new layer bridge, removes the clip from |
| // the canvas. Since clips is automatically applied to paint canvas, the image |
| // already contains clip and the image needs to be drawn before the clip stack |
| // is re-applied, it needs to remove clip from canvas and restore it after the |
| // image is drawn. |
| canvas->restoreToCount(1); |
| canvas->save(); |
| |
| // TODO(jochin): Consider using ResourceProvider()->RestoreBackBuffer() here |
| // to avoid all of this clip stack manipulation. |
| if (image) { |
| auto paint_image = image->PaintImageForCurrentFrame(); |
| if (!canvas2d_bridge_->IsAccelerated() && paint_image.IsTextureBacked()) { |
| // If new bridge is unaccelrated we must read back |paint_image| here. |
| // DrawFullImage will record the image and potentially raster on a worker |
| // thread, but texture backed PaintImages can't be used on a different |
| // thread. |
| auto sk_image = paint_image.GetSwSkImage(); |
| auto content_id = paint_image.GetContentIdForFrame(0); |
| auto builder = |
| cc::PaintImageBuilder::WithProperties(std::move(paint_image)) |
| .set_image(sk_image, content_id); |
| paint_image = builder.TakePaintImage(); |
| } |
| canvas2d_bridge_->DrawFullImage(paint_image); |
| } |
| |
| RestoreCanvasMatrixClipStack(canvas); |
| canvas2d_bridge_->DidRestoreCanvasMatrixClipStack(canvas); |
| |
| UpdateMemoryUsage(); |
| } |
| |
| CanvasResourceProvider* HTMLCanvasElement::GetOrCreateCanvasResourceProvider( |
| RasterModeHint hint) { |
| if (IsRenderingContext2D()) |
| return GetOrCreateCanvas2DLayerBridge()->GetOrCreateResourceProvider(); |
| |
| return CanvasRenderingContextHost::GetOrCreateCanvasResourceProvider(hint); |
| } |
| |
| bool HTMLCanvasElement::HasImageBitmapContext() const { |
| if (!context_) |
| return false; |
| CanvasRenderingContext::ContextType type = context_->GetContextType(); |
| return (type == CanvasRenderingContext::kContextImageBitmap); |
| } |
| |
| scoped_refptr<StaticBitmapImage> HTMLCanvasElement::GetTransparentImage() { |
| if (!transparent_image_ || transparent_image_.get()->Size() != Size()) |
| transparent_image_ = CreateTransparentImage(Size()); |
| return transparent_image_; |
| } |
| |
| cc::Layer* HTMLCanvasElement::ContentsCcLayer() const { |
| if (surface_layer_bridge_) |
| return surface_layer_bridge_->GetCcLayer(); |
| if (context_ && context_->IsComposited()) |
| return context_->CcLayer(); |
| return nullptr; |
| } |
| |
| void HTMLCanvasElement::OnContentsCcLayerChanged() { |
| // We need to repaint the layer because the foreign layer display item may |
| // appear, disappear or change. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| GetLayoutObject() && GetLayoutObject()->HasLayer()) |
| GetLayoutBoxModelObject()->Layer()->SetNeedsRepaint(); |
| } |
| |
| RespectImageOrientationEnum HTMLCanvasElement::RespectImageOrientation() const { |
| return LayoutObject::ShouldRespectImageOrientation(GetLayoutObject()); |
| } |
| |
| } // namespace blink |