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