1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h"
6 
7 #include "base/feature_list.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "third_party/blink/public/common/features.h"
11 #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
12 #include "third_party/blink/public/platform/platform.h"
13 #include "third_party/blink/renderer/bindings/core/v8/v8_image_encode_options.h"
14 #include "third_party/blink/renderer/core/html/canvas/canvas_async_blob_creator.h"
15 #include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
16 #include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h"
17 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
18 #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
19 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
20 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
21 #include "third_party/blink/renderer/platform/heap/heap.h"
22 #include "third_party/skia/include/core/SkSurface.h"
23 
24 namespace blink {
25 
CanvasRenderingContextHost(HostType host_type)26 CanvasRenderingContextHost::CanvasRenderingContextHost(HostType host_type)
27     : host_type_(host_type) {}
28 
RecordCanvasSizeToUMA(const IntSize & size)29 void CanvasRenderingContextHost::RecordCanvasSizeToUMA(const IntSize& size) {
30   if (did_record_canvas_size_to_uma_)
31     return;
32   did_record_canvas_size_to_uma_ = true;
33 
34   if (host_type_ == kCanvasHost) {
35     UMA_HISTOGRAM_CUSTOM_COUNTS("Blink.Canvas.SqrtNumberOfPixels",
36                                 std::sqrt(size.Area()), 1, 5000, 100);
37   } else if (host_type_ == kOffscreenCanvasHost) {
38     UMA_HISTOGRAM_CUSTOM_COUNTS("Blink.OffscreenCanvas.SqrtNumberOfPixels",
39                                 std::sqrt(size.Area()), 1, 5000, 100);
40   } else {
41     NOTREACHED();
42   }
43 }
44 
45 scoped_refptr<StaticBitmapImage>
CreateTransparentImage(const IntSize & size) const46 CanvasRenderingContextHost::CreateTransparentImage(const IntSize& size) const {
47   if (!IsValidImageSize(size))
48     return nullptr;
49   CanvasColorParams color_params = CanvasColorParams();
50   if (RenderingContext())
51     color_params = RenderingContext()->ColorParams();
52   SkImageInfo info = SkImageInfo::Make(
53       size.Width(), size.Height(), color_params.GetSkColorType(),
54       kPremul_SkAlphaType, color_params.GetSkColorSpace());
55   sk_sp<SkSurface> surface =
56       SkSurface::MakeRaster(info, info.minRowBytes(), nullptr);
57   if (!surface)
58     return nullptr;
59   return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
60 }
61 
Commit(scoped_refptr<CanvasResource>,const SkIRect &)62 void CanvasRenderingContextHost::Commit(scoped_refptr<CanvasResource>,
63                                         const SkIRect&) {
64   NOTIMPLEMENTED();
65 }
66 
IsPaintable() const67 bool CanvasRenderingContextHost::IsPaintable() const {
68   return (RenderingContext() && RenderingContext()->IsPaintable()) ||
69          IsValidImageSize(Size());
70 }
71 
RestoreCanvasMatrixClipStack(cc::PaintCanvas * canvas) const72 void CanvasRenderingContextHost::RestoreCanvasMatrixClipStack(
73     cc::PaintCanvas* canvas) const {
74   if (RenderingContext())
75     RenderingContext()->RestoreCanvasMatrixClipStack(canvas);
76 }
77 
Is3d() const78 bool CanvasRenderingContextHost::Is3d() const {
79   return RenderingContext() && RenderingContext()->Is3d();
80 }
81 
IsRenderingContext2D() const82 bool CanvasRenderingContextHost::IsRenderingContext2D() const {
83   return RenderingContext() && RenderingContext()->IsRenderingContext2D();
84 }
85 
86 CanvasResourceProvider*
GetOrCreateCanvasResourceProvider(RasterModeHint hint)87 CanvasRenderingContextHost::GetOrCreateCanvasResourceProvider(
88     RasterModeHint hint) {
89   return GetOrCreateCanvasResourceProviderImpl(hint);
90 }
91 
92 CanvasResourceProvider*
GetOrCreateCanvasResourceProviderImpl(RasterModeHint hint)93 CanvasRenderingContextHost::GetOrCreateCanvasResourceProviderImpl(
94     RasterModeHint hint) {
95   if (!ResourceProvider() && !did_fail_to_create_resource_provider_) {
96     if (IsValidImageSize(Size())) {
97       if (Is3d()) {
98         CreateCanvasResourceProvider3D();
99       } else {
100         CreateCanvasResourceProvider2D(hint);
101       }
102     }
103     if (!ResourceProvider())
104       did_fail_to_create_resource_provider_ = true;
105   }
106   return ResourceProvider();
107 }
108 
CreateCanvasResourceProvider3D()109 void CanvasRenderingContextHost::CreateCanvasResourceProvider3D() {
110   DCHECK(Is3d());
111 
112   base::WeakPtr<CanvasResourceDispatcher> dispatcher =
113       GetOrCreateResourceDispatcher()
114           ? GetOrCreateResourceDispatcher()->GetWeakPtr()
115           : nullptr;
116 
117   std::unique_ptr<CanvasResourceProvider> provider;
118 
119   if (SharedGpuContext::IsGpuCompositingEnabled() && LowLatencyEnabled()) {
120     // If LowLatency is enabled, we need a resource that is able to perform well
121     // in such mode. It will first try a PassThrough provider and, if that is
122     // not possible, it will try a SharedImage with the appropriate flags.
123     if ((RenderingContext() && RenderingContext()->UsingSwapChain()) ||
124         RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
125       // If either SwapChain is enabled or WebGLImage mode is enabled, we can
126       // try a passthrough provider.
127       DCHECK(LowLatencyEnabled());
128       provider = CanvasResourceProvider::CreatePassThroughProvider(
129           Size(), FilterQuality(), ColorParams(),
130           SharedGpuContext::ContextProviderWrapper(), dispatcher,
131           RenderingContext()->IsOriginTopLeft());
132     }
133     if (!provider) {
134       // If PassThrough failed, try a SharedImage with usage display enabled,
135       // and if WebGLImageChromium is enabled, add concurrent read write and
136       // usage scanout (overlay).
137       uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
138       if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
139         shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
140         shared_image_usage_flags |=
141             gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
142       }
143       provider = CanvasResourceProvider::CreateSharedImageProvider(
144           Size(), FilterQuality(), ColorParams(),
145           CanvasResourceProvider::ShouldInitialize::kCallClear,
146           SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
147           RenderingContext()->IsOriginTopLeft(), shared_image_usage_flags);
148     }
149   } else if (SharedGpuContext::IsGpuCompositingEnabled()) {
150     // If there is no LawLatency mode, and GPU is enabled, will try a GPU
151     // SharedImage that should support Usage Display and probably Usage Canbout
152     // if WebGLImageChromium is enabled.
153     uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
154     if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
155       shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
156     }
157     provider = CanvasResourceProvider::CreateSharedImageProvider(
158         Size(), FilterQuality(), ColorParams(),
159         CanvasResourceProvider::ShouldInitialize::kCallClear,
160         SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
161         RenderingContext()->IsOriginTopLeft(), shared_image_usage_flags);
162   }
163 
164   // If either of the other modes failed and / or it was not possible to do, we
165   // will backup with a SharedBitmap, and if that was not possible with a Bitmap
166   // provider.
167   if (!provider) {
168     provider = CanvasResourceProvider::CreateSharedBitmapProvider(
169         Size(), FilterQuality(), ColorParams(),
170         CanvasResourceProvider::ShouldInitialize::kCallClear, dispatcher);
171   }
172   if (!provider) {
173     provider = CanvasResourceProvider::CreateBitmapProvider(
174         Size(), FilterQuality(), ColorParams(),
175         CanvasResourceProvider::ShouldInitialize::kCallClear);
176   }
177 
178   ReplaceResourceProvider(std::move(provider));
179   if (ResourceProvider() && ResourceProvider()->IsValid()) {
180     base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
181                               ResourceProvider()->IsAccelerated());
182     base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
183                                   ResourceProvider()->GetType());
184   }
185 }
186 
CreateCanvasResourceProvider2D(RasterModeHint hint)187 void CanvasRenderingContextHost::CreateCanvasResourceProvider2D(
188     RasterModeHint hint) {
189   DCHECK(IsRenderingContext2D());
190   base::WeakPtr<CanvasResourceDispatcher> dispatcher =
191       GetOrCreateResourceDispatcher()
192           ? GetOrCreateResourceDispatcher()->GetWeakPtr()
193           : nullptr;
194 
195   std::unique_ptr<CanvasResourceProvider> provider;
196   const bool use_gpu =
197       hint == RasterModeHint::kPreferGPU && ShouldAccelerate2dContext();
198   // It is important to not use the context's IsOriginTopLeft() here
199   // because that denotes the current state and could change after the
200   // new resource provider is created e.g. due to switching between
201   // unaccelerated and accelerated modes during tab switching.
202   const bool is_origin_top_left = !use_gpu || LowLatencyEnabled();
203   if (use_gpu && LowLatencyEnabled()) {
204     // If we can use the gpu and low latency is enabled, we will try to use a
205     // SwapChain if possible.
206     if (base::FeatureList::IsEnabled(features::kLowLatencyCanvas2dSwapChain)) {
207       provider = CanvasResourceProvider::CreateSwapChainProvider(
208           Size(), FilterQuality(), ColorParams(),
209           CanvasResourceProvider::ShouldInitialize::kCallClear,
210           SharedGpuContext::ContextProviderWrapper(), dispatcher,
211           is_origin_top_left);
212     }
213     // If SwapChain failed or it was not possible, we will try a SharedImage
214     // with a set of flags trying to add Usage Display and Usage Scanout and
215     // Concurrent Read and Write if possible.
216     if (!provider) {
217       uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
218       if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled() ||
219           base::FeatureList::IsEnabled(
220               features::kLowLatencyCanvas2dImageChromium)) {
221         shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
222         shared_image_usage_flags |=
223             gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
224       }
225       provider = CanvasResourceProvider::CreateSharedImageProvider(
226           Size(), FilterQuality(), ColorParams(),
227           CanvasResourceProvider::ShouldInitialize::kCallClear,
228           SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
229           is_origin_top_left, shared_image_usage_flags);
230     }
231   } else if (use_gpu) {
232     // First try to be optimized for displaying on screen. In the case we are
233     // hardware compositing, we also try to enable the usage of the image as
234     // scanout buffer (overlay)
235     uint32_t shared_image_usage_flags = gpu::SHARED_IMAGE_USAGE_DISPLAY;
236     if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled())
237       shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
238     provider = CanvasResourceProvider::CreateSharedImageProvider(
239         Size(), FilterQuality(), ColorParams(),
240         CanvasResourceProvider::ShouldInitialize::kCallClear,
241         SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
242         is_origin_top_left, shared_image_usage_flags);
243   } else if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled()) {
244     const uint32_t shared_image_usage_flags =
245         gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
246     provider = CanvasResourceProvider::CreateSharedImageProvider(
247         Size(), FilterQuality(), ColorParams(),
248         CanvasResourceProvider::ShouldInitialize::kCallClear,
249         SharedGpuContext::ContextProviderWrapper(), RasterMode::kCPU,
250         is_origin_top_left, shared_image_usage_flags);
251   }
252 
253   // If either of the other modes failed and / or it was not possible to do, we
254   // will backup with a SharedBitmap, and if that was not possible with a Bitmap
255   // provider.
256   if (!provider) {
257     provider = CanvasResourceProvider::CreateSharedBitmapProvider(
258         Size(), FilterQuality(), ColorParams(),
259         CanvasResourceProvider::ShouldInitialize::kCallClear, dispatcher);
260   }
261   if (!provider) {
262     provider = CanvasResourceProvider::CreateBitmapProvider(
263         Size(), FilterQuality(), ColorParams(),
264         CanvasResourceProvider::ShouldInitialize::kCallClear);
265   }
266 
267   ReplaceResourceProvider(std::move(provider));
268 
269   if (ResourceProvider()) {
270     if (ResourceProvider()->IsValid()) {
271       base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
272                                 ResourceProvider()->IsAccelerated());
273       base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
274                                     ResourceProvider()->GetType());
275     }
276     ResourceProvider()->SetFilterQuality(FilterQuality());
277     ResourceProvider()->SetResourceRecyclingEnabled(true);
278   }
279 }
280 
ColorParams() const281 CanvasColorParams CanvasRenderingContextHost::ColorParams() const {
282   if (RenderingContext())
283     return RenderingContext()->ColorParams();
284   return CanvasColorParams();
285 }
286 
convertToBlob(ScriptState * script_state,const ImageEncodeOptions * options,ExceptionState & exception_state,const CanvasRenderingContext * const context)287 ScriptPromise CanvasRenderingContextHost::convertToBlob(
288     ScriptState* script_state,
289     const ImageEncodeOptions* options,
290     ExceptionState& exception_state,
291     const CanvasRenderingContext* const context) {
292   WTF::String object_name = "Canvas";
293   if (this->IsOffscreenCanvas())
294     object_name = "OffscreenCanvas";
295   std::stringstream error_msg;
296 
297   if (this->IsOffscreenCanvas() && this->IsNeutered()) {
298     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
299                                       "OffscreenCanvas object is detached.");
300     return ScriptPromise();
301   }
302 
303   if (!this->OriginClean()) {
304     error_msg << "Tainted " << object_name << " may not be exported.";
305     exception_state.ThrowSecurityError(error_msg.str().c_str());
306     return ScriptPromise();
307   }
308 
309   // It's possible that there are recorded commands that have not been resolved
310   // Finalize frame will be called in GetImage, but if there's no
311   // resourceProvider yet then the IsPaintable check will fail
312   if (RenderingContext())
313     RenderingContext()->FinalizeFrame();
314 
315   if (!this->IsPaintable() || Size().IsEmpty()) {
316     error_msg << "The size of " << object_name << " is zero.";
317     exception_state.ThrowDOMException(DOMExceptionCode::kIndexSizeError,
318                                       error_msg.str().c_str());
319     return ScriptPromise();
320   }
321 
322   if (!RenderingContext()) {
323     error_msg << object_name << " has no rendering context.";
324     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
325                                       error_msg.str().c_str());
326     return ScriptPromise();
327   }
328 
329   base::TimeTicks start_time = base::TimeTicks::Now();
330   scoped_refptr<StaticBitmapImage> image_bitmap =
331       RenderingContext()->GetImage();
332   if (image_bitmap) {
333     auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
334     CanvasAsyncBlobCreator::ToBlobFunctionType function_type =
335         CanvasAsyncBlobCreator::kHTMLCanvasConvertToBlobPromise;
336     if (this->IsOffscreenCanvas()) {
337       function_type =
338           CanvasAsyncBlobCreator::kOffscreenCanvasConvertToBlobPromise;
339     }
340     auto* execution_context = ExecutionContext::From(script_state);
341     auto* async_creator = MakeGarbageCollected<CanvasAsyncBlobCreator>(
342         image_bitmap, options, function_type, start_time, execution_context,
343         IdentifiabilityStudySettings::Get()->IsTypeAllowed(
344             IdentifiableSurface::Type::kCanvasReadback)
345             ? IdentifiabilityInputDigest(context)
346             : 0,
347         resolver);
348     async_creator->ScheduleAsyncBlobCreation(options->quality());
349     return resolver->Promise();
350   }
351   exception_state.ThrowDOMException(DOMExceptionCode::kNotReadableError,
352                                     "Readback of the source image has failed.");
353   return ScriptPromise();
354 }
355 
IsOffscreenCanvas() const356 bool CanvasRenderingContextHost::IsOffscreenCanvas() const {
357   return host_type_ == kOffscreenCanvasHost;
358 }
359 
IdentifiabilityInputDigest(const CanvasRenderingContext * const context) const360 IdentifiableToken CanvasRenderingContextHost::IdentifiabilityInputDigest(
361     const CanvasRenderingContext* const context) const {
362   const uint64_t context_digest =
363       context ? context->IdentifiableTextToken().ToUkmMetricValue() : 0;
364   const IdentifiabilityPaintOpDigest* const identifiability_paintop_digest =
365       ResourceProvider()
366           ? &(ResourceProvider()->GetIdentifiablityPaintOpDigest())
367           : nullptr;
368   const uint64_t canvas_digest =
369       identifiability_paintop_digest
370           ? identifiability_paintop_digest->GetToken().ToUkmMetricValue()
371           : 0;
372   const uint64_t context_type =
373       context ? context->GetContextType()
374               : CanvasRenderingContext::kContextTypeUnknown;
375   const bool encountered_skipped_ops =
376       (context && context->IdentifiabilityEncounteredSkippedOps()) ||
377       (identifiability_paintop_digest &&
378        identifiability_paintop_digest->encountered_skipped_ops());
379   const bool encountered_sensitive_ops =
380       context && context->IdentifiabilityEncounteredSensitiveOps();
381   const bool encountered_partially_digested_image =
382       identifiability_paintop_digest &&
383       identifiability_paintop_digest->encountered_partially_digested_image();
384   // Bits [0-3] are the context type, bits [4-6] are skipped ops, sensitive
385   // ops, and partial image ops bits, respectively. The remaining bits are
386   // for the canvas digest.
387   uint64_t final_digest =
388       ((context_digest ^ canvas_digest) << 7) | context_type;
389   if (encountered_skipped_ops)
390     final_digest |= IdentifiableSurface::CanvasTaintBit::kSkipped;
391   if (encountered_sensitive_ops)
392     final_digest |= IdentifiableSurface::CanvasTaintBit::kSensitive;
393   if (encountered_partially_digested_image)
394     final_digest |= IdentifiableSurface::CanvasTaintBit::kPartiallyDigested;
395   return final_digest;
396 }
397 
398 }  // namespace blink
399