1 /*
2 * Copyright (C) 2006 Eric Seidel <eric@webkit.org>
3 * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
4 * Copyright (C) Research In Motion Limited 2011. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
29
30 #include "base/memory/scoped_refptr.h"
31 #include "base/memory/weak_ptr.h"
32 #include "third_party/blink/public/platform/web_url.h"
33 #include "third_party/blink/public/platform/web_url_loader.h"
34 #include "third_party/blink/public/platform/web_url_loader_client.h"
35 #include "third_party/blink/public/platform/web_url_loader_factory.h"
36 #include "third_party/blink/renderer/core/animation/document_animations.h"
37 #include "third_party/blink/renderer/core/animation/document_timeline.h"
38 #include "third_party/blink/renderer/core/dom/document_parser.h"
39 #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h"
40 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
41 #include "third_party/blink/renderer/core/dom/node_traversal.h"
42 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
43 #include "third_party/blink/renderer/core/frame/local_frame.h"
44 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
45 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
46 #include "third_party/blink/renderer/core/frame/settings.h"
47 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
48 #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h"
49 #include "third_party/blink/renderer/core/layout/layout_view.h"
50 #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
51 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
52 #include "third_party/blink/renderer/core/paint/paint_layer.h"
53 #include "third_party/blink/renderer/core/style/computed_style.h"
54 #include "third_party/blink/renderer/core/svg/animation/smil_time_container.h"
55 #include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h"
56 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
57 #include "third_party/blink/renderer/core/svg/svg_fe_image_element.h"
58 #include "third_party/blink/renderer/core/svg/svg_image_element.h"
59 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
60 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
61 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
62 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
63 #include "third_party/blink/renderer/platform/graphics/color.h"
64 #include "third_party/blink/renderer/platform/graphics/dark_mode_image_classifier.h"
65 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
66 #include "third_party/blink/renderer/platform/graphics/image_observer.h"
67 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
68 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
69 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
70 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
71 #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
72 #include "third_party/blink/renderer/platform/heap/heap.h"
73 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
74
75 namespace blink {
76
77 namespace {
78
79 using TaskRunnerHandle = scheduler::WebResourceLoadingTaskRunnerHandle;
80
81 class FailingLoader final : public WebURLLoader {
82 public:
FailingLoader(std::unique_ptr<TaskRunnerHandle> task_runner_handle)83 explicit FailingLoader(std::unique_ptr<TaskRunnerHandle> task_runner_handle)
84 : task_runner_handle_(std::move(task_runner_handle)) {}
85 ~FailingLoader() override = default;
86
87 // WebURLLoader implementation:
LoadSynchronously(std::unique_ptr<network::ResourceRequest> request,scoped_refptr<WebURLRequest::ExtraData> request_extra_data,int requestor_id,bool download_to_network_cache_only,bool pass_response_pipe_to_client,bool no_mime_sniffing,base::TimeDelta timeout_interval,WebURLLoaderClient *,WebURLResponse &,base::Optional<WebURLError> & error,WebData &,int64_t & encoded_data_length,int64_t & encoded_body_length,WebBlobInfo & downloaded_blob)88 void LoadSynchronously(
89 std::unique_ptr<network::ResourceRequest> request,
90 scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
91 int requestor_id,
92 bool download_to_network_cache_only,
93 bool pass_response_pipe_to_client,
94 bool no_mime_sniffing,
95 base::TimeDelta timeout_interval,
96 WebURLLoaderClient*,
97 WebURLResponse&,
98 base::Optional<WebURLError>& error,
99 WebData&,
100 int64_t& encoded_data_length,
101 int64_t& encoded_body_length,
102 WebBlobInfo& downloaded_blob) override {
103 NOTREACHED();
104 }
LoadAsynchronously(std::unique_ptr<network::ResourceRequest> request,scoped_refptr<WebURLRequest::ExtraData> request_extra_data,int requestor_id,bool download_to_network_cache_only,bool no_mime_sniffing,WebURLLoaderClient * client)105 void LoadAsynchronously(
106 std::unique_ptr<network::ResourceRequest> request,
107 scoped_refptr<WebURLRequest::ExtraData> request_extra_data,
108 int requestor_id,
109 bool download_to_network_cache_only,
110 bool no_mime_sniffing,
111 WebURLLoaderClient* client) override {
112 NOTREACHED();
113 }
SetDefersLoading(bool)114 void SetDefersLoading(bool) override {}
DidChangePriority(WebURLRequest::Priority,int)115 void DidChangePriority(WebURLRequest::Priority, int) override {}
GetTaskRunner()116 scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() override {
117 return task_runner_handle_->GetTaskRunner();
118 }
119
120 private:
121 const std::unique_ptr<TaskRunnerHandle> task_runner_handle_;
122 };
123
124 class FailingLoaderFactory final : public WebURLLoaderFactory {
125 public:
126 // WebURLLoaderFactory implementation:
CreateURLLoader(const WebURLRequest &,std::unique_ptr<TaskRunnerHandle> task_runner_handle)127 std::unique_ptr<WebURLLoader> CreateURLLoader(
128 const WebURLRequest&,
129 std::unique_ptr<TaskRunnerHandle> task_runner_handle) override {
130 return std::make_unique<FailingLoader>(std::move(task_runner_handle));
131 }
132 };
133
134 } // namespace
135
136 // SVGImageLocalFrameClient is used to wait until SVG document's load event
137 // in the case where there are subresources asynchronously loaded.
138 //
139 // Reference cycle: SVGImage -(Persistent)-> Page -(Member)-> Frame -(Member)->
140 // FrameClient == SVGImageLocalFrameClient -(raw)-> SVGImage.
141 class SVGImage::SVGImageLocalFrameClient : public EmptyLocalFrameClient {
142 public:
SVGImageLocalFrameClient(SVGImage * image)143 SVGImageLocalFrameClient(SVGImage* image) : image_(image) {}
144
ClearImage()145 void ClearImage() { image_ = nullptr; }
146
147 private:
CreateURLLoaderFactory()148 std::unique_ptr<WebURLLoaderFactory> CreateURLLoaderFactory() override {
149 // SVG Images have unique security rules that prevent all subresource
150 // requests except for data urls.
151 return std::make_unique<FailingLoaderFactory>();
152 }
153
DispatchDidHandleOnloadEvents()154 void DispatchDidHandleOnloadEvents() override {
155 // The SVGImage was destructed before SVG load completion.
156 if (!image_)
157 return;
158
159 image_->LoadCompleted();
160 }
161
162 // Cleared manually by SVGImage's destructor when |image_| is destructed.
163 SVGImage* image_;
164 };
165
SVGImage(ImageObserver * observer,bool is_multipart)166 SVGImage::SVGImage(ImageObserver* observer, bool is_multipart)
167 : Image(observer, is_multipart),
168 paint_controller_(std::make_unique<PaintController>()),
169 has_pending_timeline_rewind_(false) {}
170
~SVGImage()171 SVGImage::~SVGImage() {
172 if (frame_client_)
173 frame_client_->ClearImage();
174
175 if (page_) {
176 // It is safe to allow UA events within this scope, because event
177 // dispatching inside the SVG image's document doesn't trigger JavaScript
178 // execution. All script execution is forbidden when an SVG is loaded as an
179 // image subresource - see SetScriptEnabled in SVGImage::DataChanged().
180 EventDispatchForbiddenScope::AllowUserAgentEvents allow_events;
181 // Store m_page in a local variable, clearing m_page, so that
182 // SVGImageChromeClient knows we're destructed.
183 Page* current_page = page_.Release();
184 // Break both the loader and view references to the frame
185 current_page->WillBeDestroyed();
186 }
187
188 // Verify that page teardown destroyed the Chrome
189 DCHECK(!chrome_client_ || !chrome_client_->GetImage());
190 }
191
IsInSVGImage(const Node * node)192 bool SVGImage::IsInSVGImage(const Node* node) {
193 DCHECK(node);
194
195 Page* page = node->GetDocument().GetPage();
196 if (!page)
197 return false;
198
199 return page->GetChromeClient().IsSVGImageChromeClient();
200 }
201
CheckLoaded() const202 void SVGImage::CheckLoaded() const {
203 CHECK(page_);
204
205 auto* frame = To<LocalFrame>(page_->MainFrame());
206
207 // Failures of this assertion might result in wrong origin tainting checks,
208 // because CurrentFrameHasSingleSecurityOrigin() assumes all subresources of
209 // the SVG are loaded and thus ready for origin checks.
210 CHECK(frame->GetDocument()->LoadEventFinished());
211 }
212
CurrentFrameHasSingleSecurityOrigin() const213 bool SVGImage::CurrentFrameHasSingleSecurityOrigin() const {
214 if (!page_)
215 return true;
216
217 auto* frame = To<LocalFrame>(page_->MainFrame());
218
219 CheckLoaded();
220
221 SVGSVGElement* root_element =
222 frame->GetDocument()->AccessSVGExtensions().rootElement();
223 if (!root_element)
224 return true;
225
226 // Don't allow foreignObject elements or images that are not known to be
227 // single-origin since these can leak cross-origin information.
228 for (Node* node = root_element; node; node = FlatTreeTraversal::Next(*node)) {
229 if (IsA<SVGForeignObjectElement>(*node))
230 return false;
231 if (auto* image = DynamicTo<SVGImageElement>(*node)) {
232 if (!image->CurrentFrameHasSingleSecurityOrigin())
233 return false;
234 } else if (auto* fe_image = DynamicTo<SVGFEImageElement>(*node)) {
235 if (!fe_image->CurrentFrameHasSingleSecurityOrigin())
236 return false;
237 }
238 }
239
240 // Because SVG image rendering disallows external resources and links, these
241 // images effectively are restricted to a single security origin.
242 return true;
243 }
244
SvgRootElement(Page * page)245 static SVGSVGElement* SvgRootElement(Page* page) {
246 if (!page)
247 return nullptr;
248 auto* frame = To<LocalFrame>(page->MainFrame());
249 return frame->GetDocument()->AccessSVGExtensions().rootElement();
250 }
251
ContainerSize() const252 LayoutSize SVGImage::ContainerSize() const {
253 SVGSVGElement* root_element = SvgRootElement(page_.Get());
254 if (!root_element)
255 return LayoutSize();
256
257 LayoutSVGRoot* layout_object =
258 ToLayoutSVGRoot(root_element->GetLayoutObject());
259 if (!layout_object)
260 return LayoutSize();
261
262 // If a container size is available it has precedence.
263 LayoutSize container_size = layout_object->ContainerSize();
264 if (!container_size.IsEmpty())
265 return container_size;
266
267 // Assure that a container size is always given for a non-identity zoom level.
268 DCHECK_EQ(layout_object->StyleRef().EffectiveZoom(), 1);
269
270 // No set container size; use concrete object size.
271 return intrinsic_size_;
272 }
273
Size() const274 IntSize SVGImage::Size() const {
275 return RoundedIntSize(intrinsic_size_);
276 }
277
ResolveWidthForRatio(float height,const FloatSize & intrinsic_ratio)278 static float ResolveWidthForRatio(float height,
279 const FloatSize& intrinsic_ratio) {
280 return height * intrinsic_ratio.Width() / intrinsic_ratio.Height();
281 }
282
ResolveHeightForRatio(float width,const FloatSize & intrinsic_ratio)283 static float ResolveHeightForRatio(float width,
284 const FloatSize& intrinsic_ratio) {
285 return width * intrinsic_ratio.Height() / intrinsic_ratio.Width();
286 }
287
HasIntrinsicDimensions() const288 bool SVGImage::HasIntrinsicDimensions() const {
289 return !ConcreteObjectSize(FloatSize()).IsEmpty();
290 }
291
HasIntrinsicSizingInfo() const292 bool SVGImage::HasIntrinsicSizingInfo() const {
293 SVGSVGElement* svg = SvgRootElement(page_.Get());
294 return svg && svg->GetLayoutObject();
295 }
296
GetIntrinsicSizingInfo(IntrinsicSizingInfo & intrinsic_sizing_info) const297 bool SVGImage::GetIntrinsicSizingInfo(
298 IntrinsicSizingInfo& intrinsic_sizing_info) const {
299 SVGSVGElement* svg = SvgRootElement(page_.Get());
300 if (!svg)
301 return false;
302
303 LayoutSVGRoot* layout_object = ToLayoutSVGRoot(svg->GetLayoutObject());
304 if (!layout_object)
305 return false;
306
307 layout_object->UnscaledIntrinsicSizingInfo(intrinsic_sizing_info);
308 return true;
309 }
310
ConcreteObjectSize(const FloatSize & default_object_size) const311 FloatSize SVGImage::ConcreteObjectSize(
312 const FloatSize& default_object_size) const {
313 IntrinsicSizingInfo intrinsic_sizing_info;
314 if (!GetIntrinsicSizingInfo(intrinsic_sizing_info))
315 return FloatSize();
316
317 // https://www.w3.org/TR/css3-images/#default-sizing
318 if (intrinsic_sizing_info.has_width && intrinsic_sizing_info.has_height)
319 return intrinsic_sizing_info.size;
320
321 // We're not using an intrinsic aspect ratio to resolve a missing
322 // intrinsic width or height when preserveAspectRatio is none.
323 // (Ref: crbug.com/584172)
324 SVGSVGElement* svg = SvgRootElement(page_.Get());
325 if (svg->preserveAspectRatio()->CurrentValue()->Align() ==
326 SVGPreserveAspectRatio::kSvgPreserveaspectratioNone)
327 return default_object_size;
328
329 if (intrinsic_sizing_info.has_width) {
330 if (intrinsic_sizing_info.aspect_ratio.IsEmpty())
331 return FloatSize(intrinsic_sizing_info.size.Width(),
332 default_object_size.Height());
333
334 return FloatSize(intrinsic_sizing_info.size.Width(),
335 ResolveHeightForRatio(intrinsic_sizing_info.size.Width(),
336 intrinsic_sizing_info.aspect_ratio));
337 }
338
339 if (intrinsic_sizing_info.has_height) {
340 if (intrinsic_sizing_info.aspect_ratio.IsEmpty())
341 return FloatSize(default_object_size.Width(),
342 intrinsic_sizing_info.size.Height());
343
344 return FloatSize(ResolveWidthForRatio(intrinsic_sizing_info.size.Height(),
345 intrinsic_sizing_info.aspect_ratio),
346 intrinsic_sizing_info.size.Height());
347 }
348
349 if (!intrinsic_sizing_info.aspect_ratio.IsEmpty()) {
350 // "A contain constraint is resolved by setting the concrete object size to
351 // the largest rectangle that has the object's intrinsic aspect ratio and
352 // additionally has neither width nor height larger than the constraint
353 // rectangle's width and height, respectively."
354 float solution_width = ResolveWidthForRatio(
355 default_object_size.Height(), intrinsic_sizing_info.aspect_ratio);
356 if (solution_width <= default_object_size.Width())
357 return FloatSize(solution_width, default_object_size.Height());
358
359 float solution_height = ResolveHeightForRatio(
360 default_object_size.Width(), intrinsic_sizing_info.aspect_ratio);
361 return FloatSize(default_object_size.Width(), solution_height);
362 }
363
364 return default_object_size;
365 }
366
367 template <typename Func>
ForContainer(const FloatSize & container_size,Func && func)368 void SVGImage::ForContainer(const FloatSize& container_size, Func&& func) {
369 if (!page_)
370 return;
371
372 // Temporarily disable the image observer to prevent changeInRect() calls due
373 // re-laying out the image.
374 ImageObserverDisabler image_observer_disabler(this);
375
376 LayoutSize rounded_container_size = RoundedLayoutSize(container_size);
377
378 if (SVGSVGElement* root_element = SvgRootElement(page_.Get())) {
379 if (LayoutSVGRoot* layout_object =
380 ToLayoutSVGRoot(root_element->GetLayoutObject()))
381 layout_object->SetContainerSize(rounded_container_size);
382 }
383
384 func(FloatSize(rounded_container_size.Width() / container_size.Width(),
385 rounded_container_size.Height() / container_size.Height()));
386 }
387
DrawForContainer(cc::PaintCanvas * canvas,const PaintFlags & flags,const FloatSize & container_size,float zoom,const FloatRect & dst_rect,const FloatRect & src_rect,const KURL & url)388 void SVGImage::DrawForContainer(cc::PaintCanvas* canvas,
389 const PaintFlags& flags,
390 const FloatSize& container_size,
391 float zoom,
392 const FloatRect& dst_rect,
393 const FloatRect& src_rect,
394 const KURL& url) {
395 ForContainer(container_size, [&](const FloatSize& residual_scale) {
396 FloatRect scaled_src = src_rect;
397 scaled_src.Scale(1 / zoom);
398
399 // Compensate for the container size rounding by adjusting the source rect.
400 FloatSize adjusted_src_size = scaled_src.Size();
401 adjusted_src_size.Scale(residual_scale.Width(), residual_scale.Height());
402 scaled_src.SetSize(adjusted_src_size);
403
404 DrawInternal(canvas, flags, dst_rect, scaled_src, kRespectImageOrientation,
405 kClampImageToSourceRect, url);
406 });
407 }
408
PaintImageForCurrentFrame()409 PaintImage SVGImage::PaintImageForCurrentFrame() {
410 auto builder =
411 CreatePaintImageBuilder().set_completion_state(completion_state());
412 PopulatePaintRecordForCurrentFrameForContainer(builder, NullURL(), Size());
413 return builder.TakePaintImage();
414 }
415
DrawPatternForContainer(GraphicsContext & context,const FloatSize container_size,float zoom,const FloatRect & src_rect,const FloatSize & tile_scale,const FloatPoint & phase,SkBlendMode composite_op,const FloatRect & dst_rect,const FloatSize & repeat_spacing,const KURL & url)416 void SVGImage::DrawPatternForContainer(GraphicsContext& context,
417 const FloatSize container_size,
418 float zoom,
419 const FloatRect& src_rect,
420 const FloatSize& tile_scale,
421 const FloatPoint& phase,
422 SkBlendMode composite_op,
423 const FloatRect& dst_rect,
424 const FloatSize& repeat_spacing,
425 const KURL& url) {
426 // Tile adjusted for scaling/stretch.
427 FloatRect tile(src_rect);
428 tile.Scale(tile_scale.Width(), tile_scale.Height());
429
430 // Expand the tile to account for repeat spacing.
431 FloatRect spaced_tile(tile);
432 spaced_tile.Expand(FloatSize(repeat_spacing));
433
434 PaintRecordBuilder builder(nullptr, &context);
435 {
436 DrawingRecorder recorder(builder.Context(), builder,
437 DisplayItem::Type::kSVGImage);
438 // When generating an expanded tile, make sure we don't draw into the
439 // spacing area.
440 if (tile != spaced_tile)
441 builder.Context().Clip(tile);
442 PaintFlags flags;
443 DrawForContainer(builder.Context().Canvas(), flags, container_size, zoom,
444 tile, src_rect, url);
445 }
446 sk_sp<PaintRecord> record = builder.EndRecording();
447
448 SkMatrix pattern_transform;
449 pattern_transform.setTranslate(phase.X() + spaced_tile.X(),
450 phase.Y() + spaced_tile.Y());
451
452 PaintFlags flags;
453 flags.setShader(
454 PaintShader::MakePaintRecord(record, spaced_tile, SkTileMode::kRepeat,
455 SkTileMode::kRepeat, &pattern_transform));
456 // If the shader could not be instantiated (e.g. non-invertible matrix),
457 // draw transparent.
458 // Note: we can't simply bail, because of arbitrary blend mode.
459 if (!flags.HasShader())
460 flags.setColor(SK_ColorTRANSPARENT);
461
462 flags.setBlendMode(composite_op);
463 flags.setColorFilter(sk_ref_sp(context.GetColorFilter()));
464 context.DrawRect(dst_rect, flags);
465
466 StartAnimation();
467 }
468
PaintRecordForContainer(const KURL & url,const IntSize & container_size,const IntRect & draw_src_rect,const IntRect & draw_dst_rect,bool flip_y)469 sk_sp<PaintRecord> SVGImage::PaintRecordForContainer(
470 const KURL& url,
471 const IntSize& container_size,
472 const IntRect& draw_src_rect,
473 const IntRect& draw_dst_rect,
474 bool flip_y) {
475 if (!page_)
476 return nullptr;
477
478 PaintRecorder recorder;
479 cc::PaintCanvas* canvas = recorder.beginRecording(draw_src_rect);
480 if (flip_y) {
481 canvas->translate(0, draw_dst_rect.Height());
482 canvas->scale(1, -1);
483 }
484 DrawForContainer(canvas, PaintFlags(), FloatSize(container_size), 1,
485 FloatRect(draw_dst_rect), FloatRect(draw_src_rect), url);
486 return recorder.finishRecordingAsPicture();
487 }
488
PopulatePaintRecordForCurrentFrameForContainer(PaintImageBuilder & builder,const KURL & url,const IntSize & container_size)489 void SVGImage::PopulatePaintRecordForCurrentFrameForContainer(
490 PaintImageBuilder& builder,
491 const KURL& url,
492 const IntSize& container_size) {
493 if (!page_)
494 return;
495
496 const IntRect container_rect(IntPoint(), container_size);
497
498 PaintRecorder recorder;
499 cc::PaintCanvas* canvas = recorder.beginRecording(container_rect);
500 DrawForContainer(canvas, PaintFlags(), FloatSize(container_rect.Size()), 1,
501 FloatRect(container_rect), FloatRect(container_rect), url);
502 builder.set_paint_record(recorder.finishRecordingAsPicture(), container_rect,
503 PaintImage::GetNextContentId());
504 }
505
DrawNeedsLayer(const PaintFlags & flags)506 static bool DrawNeedsLayer(const PaintFlags& flags) {
507 if (SkColorGetA(flags.getColor()) < 255)
508 return true;
509
510 // This is needed to preserve the dark mode filter that
511 // has been set in GraphicsContext.
512 if (flags.getColorFilter())
513 return true;
514
515 return flags.getBlendMode() != SkBlendMode::kSrcOver;
516 }
517
ApplyShaderInternal(PaintFlags & flags,const SkMatrix & local_matrix,const KURL & url)518 bool SVGImage::ApplyShaderInternal(PaintFlags& flags,
519 const SkMatrix& local_matrix,
520 const KURL& url) {
521 const FloatSize size(ContainerSize());
522 if (size.IsEmpty())
523 return false;
524
525 FloatRect bounds(FloatPoint(), size);
526 flags.setShader(PaintShader::MakePaintRecord(
527 PaintRecordForCurrentFrame(url), bounds, SkTileMode::kRepeat,
528 SkTileMode::kRepeat, &local_matrix));
529
530 // Animation is normally refreshed in draw() impls, which we don't reach when
531 // painting via shaders.
532 StartAnimation();
533
534 return true;
535 }
536
ApplyShader(PaintFlags & flags,const SkMatrix & local_matrix)537 bool SVGImage::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) {
538 return ApplyShaderInternal(flags, local_matrix, NullURL());
539 }
540
ApplyShaderForContainer(const FloatSize & container_size,float zoom,const KURL & url,PaintFlags & flags,const SkMatrix & local_matrix)541 bool SVGImage::ApplyShaderForContainer(const FloatSize& container_size,
542 float zoom,
543 const KURL& url,
544 PaintFlags& flags,
545 const SkMatrix& local_matrix) {
546 bool result = false;
547 ForContainer(container_size, [&](const FloatSize& residual_scale) {
548 // Compensate for the container size rounding.
549 auto adjusted_local_matrix = local_matrix;
550 adjusted_local_matrix.preScale(zoom * residual_scale.Width(),
551 zoom * residual_scale.Height());
552
553 result = ApplyShaderInternal(flags, adjusted_local_matrix, url);
554 });
555
556 return result;
557 }
558
Draw(cc::PaintCanvas * canvas,const PaintFlags & flags,const FloatRect & dst_rect,const FloatRect & src_rect,RespectImageOrientationEnum should_respect_image_orientation,ImageClampingMode clamp_mode,ImageDecodingMode)559 void SVGImage::Draw(
560 cc::PaintCanvas* canvas,
561 const PaintFlags& flags,
562 const FloatRect& dst_rect,
563 const FloatRect& src_rect,
564 RespectImageOrientationEnum should_respect_image_orientation,
565 ImageClampingMode clamp_mode,
566 ImageDecodingMode) {
567 if (!page_)
568 return;
569
570 DrawInternal(canvas, flags, dst_rect, src_rect,
571 should_respect_image_orientation, clamp_mode, NullURL());
572 }
573
PaintRecordForCurrentFrame(const KURL & url)574 sk_sp<PaintRecord> SVGImage::PaintRecordForCurrentFrame(const KURL& url) {
575 DCHECK(page_);
576 LocalFrameView* view = To<LocalFrame>(page_->MainFrame())->View();
577 IntSize rounded_container_size = RoundedIntSize(ContainerSize());
578 view->Resize(rounded_container_size);
579 page_->GetVisualViewport().SetSize(rounded_container_size);
580
581 // Always call processUrlFragment, even if the url is empty, because
582 // there may have been a previous url/fragment that needs to be reset.
583 view->ProcessUrlFragment(url, /*same_document_navigation=*/false);
584
585 // If the image was reset, we need to rewind the timeline back to 0. This
586 // needs to be done before painting, or else we wouldn't get the correct
587 // reset semantics (we'd paint the "last" frame rather than the one at
588 // time=0.) The reason we do this here and not in resetAnimation() is to
589 // avoid setting timers from the latter.
590 FlushPendingTimelineRewind();
591
592 if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
593 view->UpdateAllLifecyclePhases(DocumentUpdateReason::kSVGImage);
594 return view->GetPaintRecord();
595 }
596
597 view->UpdateAllLifecyclePhasesExceptPaint(DocumentUpdateReason::kSVGImage);
598 PaintRecordBuilder builder(nullptr, nullptr, paint_controller_.get());
599 view->PaintOutsideOfLifecycle(builder.Context(), kGlobalPaintNormalPhase);
600 return builder.EndRecording();
601 }
602
DrawInternal(cc::PaintCanvas * canvas,const PaintFlags & flags,const FloatRect & dst_rect,const FloatRect & src_rect,RespectImageOrientationEnum,ImageClampingMode,const KURL & url)603 void SVGImage::DrawInternal(cc::PaintCanvas* canvas,
604 const PaintFlags& flags,
605 const FloatRect& dst_rect,
606 const FloatRect& src_rect,
607 RespectImageOrientationEnum,
608 ImageClampingMode,
609 const KURL& url) {
610 {
611 PaintCanvasAutoRestore ar(canvas, false);
612 if (DrawNeedsLayer(flags)) {
613 SkRect layer_rect = dst_rect;
614 canvas->saveLayer(&layer_rect, &flags);
615 }
616 // We can only draw the entire frame, clipped to the rect we want. So
617 // compute where the top left of the image would be if we were drawing
618 // without clipping, and translate accordingly.
619 canvas->save();
620 canvas->clipRect(EnclosingIntRect(dst_rect));
621 canvas->concat(SkMatrix::MakeRectToRect(src_rect, dst_rect,
622 SkMatrix::kFill_ScaleToFit));
623 canvas->drawPicture(PaintRecordForCurrentFrame(url));
624 canvas->restore();
625 }
626
627 // Start any (SMIL) animations if needed. This will restart or continue
628 // animations if preceded by calls to resetAnimation or stopAnimation
629 // respectively.
630 StartAnimation();
631 }
632
ScheduleTimelineRewind()633 void SVGImage::ScheduleTimelineRewind() {
634 has_pending_timeline_rewind_ = true;
635 }
636
FlushPendingTimelineRewind()637 void SVGImage::FlushPendingTimelineRewind() {
638 if (!has_pending_timeline_rewind_)
639 return;
640 if (SVGSVGElement* root_element = SvgRootElement(page_.Get()))
641 root_element->setCurrentTime(0);
642 has_pending_timeline_rewind_ = false;
643 }
644
StartAnimation()645 void SVGImage::StartAnimation() {
646 SVGSVGElement* root_element = SvgRootElement(page_.Get());
647 if (!root_element)
648 return;
649 chrome_client_->ResumeAnimation();
650 if (root_element->animationsPaused())
651 root_element->unpauseAnimations();
652 }
653
StopAnimation()654 void SVGImage::StopAnimation() {
655 SVGSVGElement* root_element = SvgRootElement(page_.Get());
656 if (!root_element)
657 return;
658 chrome_client_->SuspendAnimation();
659 root_element->pauseAnimations();
660 }
661
ResetAnimation()662 void SVGImage::ResetAnimation() {
663 SVGSVGElement* root_element = SvgRootElement(page_.Get());
664 if (!root_element)
665 return;
666 chrome_client_->SuspendAnimation();
667 root_element->pauseAnimations();
668 ScheduleTimelineRewind();
669 }
670
RestoreAnimation()671 void SVGImage::RestoreAnimation() {
672 // If the image has no animations then do nothing.
673 if (!MaybeAnimated())
674 return;
675 // If there are no clients, or no client is going to render, then do nothing.
676 ImageObserver* image_observer = GetImageObserver();
677 if (!image_observer || image_observer->ShouldPauseAnimation(this))
678 return;
679 StartAnimation();
680 }
681
MaybeAnimated()682 bool SVGImage::MaybeAnimated() {
683 SVGSVGElement* root_element = SvgRootElement(page_.Get());
684 if (!root_element)
685 return false;
686 return root_element->TimeContainer()->HasAnimations() ||
687 To<LocalFrame>(page_->MainFrame())
688 ->GetDocument()
689 ->Timeline()
690 .HasPendingUpdates();
691 }
692
ServiceAnimations(base::TimeTicks monotonic_animation_start_time)693 void SVGImage::ServiceAnimations(
694 base::TimeTicks monotonic_animation_start_time) {
695 if (!GetImageObserver())
696 return;
697
698 // If none of our observers (sic!) are visible, or for some other reason
699 // does not want us to keep running animations, stop them until further
700 // notice (next paint.)
701 if (GetImageObserver()->ShouldPauseAnimation(this)) {
702 StopAnimation();
703 return;
704 }
705
706 // serviceScriptedAnimations runs requestAnimationFrame callbacks, but SVG
707 // images can't have any so we assert there's no script.
708 ScriptForbiddenScope forbid_script;
709
710 // The calls below may trigger GCs, so set up the required persistent
711 // reference on the ImageResourceContent which owns this SVGImage. By
712 // transitivity, that will keep the associated SVGImageChromeClient object
713 // alive.
714 Persistent<ImageObserver> protect(GetImageObserver());
715 page_->Animator().ServiceScriptedAnimations(monotonic_animation_start_time);
716
717 // Do *not* update the paint phase. It's critical to paint only when
718 // actually generating painted output, not only for performance reasons,
719 // but to preserve correct coherence of the cache of the output with
720 // the needsRepaint bits of the PaintLayers in the image.
721 LocalFrameView* frame_view = To<LocalFrame>(page_->MainFrame())->View();
722 frame_view->UpdateAllLifecyclePhasesExceptPaint(
723 DocumentUpdateReason::kSVGImage);
724
725 // We run UpdateAnimations after the paint phase, but per the above comment,
726 // we don't want to run lifecycle through to paint for SVG images. Since we
727 // know SVG images never have composited animations, we can update animations
728 // directly without worrying about including PaintArtifactCompositor's
729 // analysis of whether animations should be composited.
730 frame_view->GetLayoutView()
731 ->GetDocument()
732 .GetDocumentAnimations()
733 .UpdateAnimations(DocumentLifecycle::kLayoutClean, nullptr);
734 }
735
AdvanceAnimationForTesting()736 void SVGImage::AdvanceAnimationForTesting() {
737 if (SVGSVGElement* root_element = SvgRootElement(page_.Get())) {
738 root_element->TimeContainer()->AdvanceFrameForTesting();
739
740 // The following triggers animation updates which can issue a new draw
741 // and temporarily change the animation timeline. It's necessary to call
742 // reset before changing to a time value as animation clock does not
743 // expect to go backwards.
744 base::TimeTicks current_animation_time =
745 page_->Animator().Clock().CurrentTime();
746 page_->Animator().Clock().ResetTimeForTesting();
747 if (root_element->TimeContainer()->IsStarted())
748 root_element->TimeContainer()->ResetDocumentTime();
749 page_->Animator().ServiceScriptedAnimations(
750 root_element->GetDocument().Timeline().ZeroTime() +
751 base::TimeDelta::FromSecondsD(root_element->getCurrentTime()));
752 GetImageObserver()->Changed(this);
753 page_->Animator().Clock().ResetTimeForTesting();
754 page_->Animator().Clock().UpdateTime(current_animation_time);
755 }
756 }
757
ChromeClientForTesting()758 SVGImageChromeClient& SVGImage::ChromeClientForTesting() {
759 return *chrome_client_;
760 }
761
UpdateUseCounters(const Document & document) const762 void SVGImage::UpdateUseCounters(const Document& document) const {
763 if (SVGSVGElement* root_element = SvgRootElement(page_.Get())) {
764 if (root_element->TimeContainer()->HasAnimations()) {
765 document.CountUse(WebFeature::kSVGSMILAnimationInImageRegardlessOfCache);
766 }
767 }
768 }
769
LoadCompleted()770 void SVGImage::LoadCompleted() {
771 switch (load_state_) {
772 case kInDataChanged:
773 load_state_ = kLoadCompleted;
774 break;
775
776 case kWaitingForAsyncLoadCompletion:
777 load_state_ = kLoadCompleted;
778
779 // Because LoadCompleted() is called synchronously from
780 // Document::ImplicitClose(), we defer AsyncLoadCompleted() to avoid
781 // potential bugs and timing dependencies around ImplicitClose() and
782 // to make LoadEventFinished() true when AsyncLoadCompleted() is called.
783 To<LocalFrame>(page_->MainFrame())
784 ->GetTaskRunner(TaskType::kInternalLoading)
785 ->PostTask(FROM_HERE, WTF::Bind(&SVGImage::NotifyAsyncLoadCompleted,
786 scoped_refptr<SVGImage>(this)));
787 break;
788
789 case kDataChangedNotStarted:
790 case kLoadCompleted:
791 CHECK(false);
792 break;
793 }
794 }
795
NotifyAsyncLoadCompleted()796 void SVGImage::NotifyAsyncLoadCompleted() {
797 if (GetImageObserver())
798 GetImageObserver()->AsyncLoadCompleted(this);
799 }
800
DataChanged(bool all_data_received)801 Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) {
802 TRACE_EVENT0("blink", "SVGImage::dataChanged");
803
804 // Don't do anything if is an empty image.
805 if (!Data()->size())
806 return kSizeAvailable;
807
808 if (!all_data_received)
809 return page_ ? kSizeAvailable : kSizeUnavailable;
810
811 CHECK(!page_);
812
813 // SVGImage will fire events (and the default C++ handlers run) but doesn't
814 // actually allow script to run so it's fine to call into it. We allow this
815 // since it means an SVG data url can synchronously load like other image
816 // types.
817 EventDispatchForbiddenScope::AllowUserAgentEvents allow_user_agent_events;
818
819 CHECK_EQ(load_state_, kDataChangedNotStarted);
820 load_state_ = kInDataChanged;
821
822 Page::PageClients page_clients;
823 FillWithEmptyClients(page_clients);
824 chrome_client_ = MakeGarbageCollected<SVGImageChromeClient>(this);
825 page_clients.chrome_client = chrome_client_.Get();
826
827 // FIXME: If this SVG ends up loading itself, we might leak the world.
828 // The Cache code does not know about ImageResources holding Frames and
829 // won't know to break the cycle.
830 // This will become an issue when SVGImage will be able to load other
831 // SVGImage objects, but we're safe now, because SVGImage can only be
832 // loaded by a top-level document.
833 Page* page;
834 {
835 TRACE_EVENT0("blink", "SVGImage::dataChanged::createPage");
836 page = Page::CreateNonOrdinary(page_clients);
837 page->GetSettings().SetScriptEnabled(false);
838 page->GetSettings().SetPluginsEnabled(false);
839
840 // Because this page is detached, it can't get default font settings
841 // from the embedder. Copy over font settings so we have sensible
842 // defaults. These settings are fixed and will not update if changed.
843 if (!Page::OrdinaryPages().IsEmpty()) {
844 Settings& default_settings =
845 (*Page::OrdinaryPages().begin())->GetSettings();
846 page->GetSettings().GetGenericFontFamilySettings() =
847 default_settings.GetGenericFontFamilySettings();
848 page->GetSettings().SetMinimumFontSize(
849 default_settings.GetMinimumFontSize());
850 page->GetSettings().SetMinimumLogicalFontSize(
851 default_settings.GetMinimumLogicalFontSize());
852 page->GetSettings().SetDefaultFontSize(
853 default_settings.GetDefaultFontSize());
854 page->GetSettings().SetDefaultFixedFontSize(
855 default_settings.GetDefaultFixedFontSize());
856
857 // Also copy the preferred-color-scheme to ensure a responsiveness to
858 // dark/light color schemes.
859 page->GetSettings().SetPreferredColorScheme(
860 default_settings.GetPreferredColorScheme());
861 }
862 }
863
864 LocalFrame* frame = nullptr;
865 {
866 TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame");
867 DCHECK(!frame_client_);
868 frame_client_ = MakeGarbageCollected<SVGImageLocalFrameClient>(this);
869 frame = MakeGarbageCollected<LocalFrame>(frame_client_, *page, nullptr,
870 nullptr, nullptr);
871 frame->SetView(MakeGarbageCollected<LocalFrameView>(*frame));
872 frame->Init();
873 }
874
875 FrameLoader& loader = frame->Loader();
876 loader.ForceSandboxFlags(mojom::blink::WebSandboxFlags::kAll);
877
878 // SVG Images will always synthesize a viewBox, if it's not available, and
879 // thus never see scrollbars.
880 frame->View()->SetCanHaveScrollbars(false);
881 // SVG Images are transparent.
882 frame->View()->SetBaseBackgroundColor(Color::kTransparent);
883
884 page_ = page;
885
886 TRACE_EVENT0("blink", "SVGImage::dataChanged::load");
887
888 frame->ForceSynchronousDocumentInstall("image/svg+xml", Data());
889
890 // Intrinsic sizing relies on computed style (e.g. font-size and
891 // writing-mode).
892 frame->GetDocument()->UpdateStyleAndLayoutTree();
893
894 // Set the concrete object size before a container size is available.
895 intrinsic_size_ = RoundedLayoutSize(ConcreteObjectSize(FloatSize(
896 LayoutReplaced::kDefaultWidth, LayoutReplaced::kDefaultHeight)));
897
898 DCHECK(page_);
899 switch (load_state_) {
900 case kInDataChanged:
901 load_state_ = kWaitingForAsyncLoadCompletion;
902 return SvgRootElement(page_.Get())
903 ? kSizeAvailableAndLoadingAsynchronously
904 : kSizeUnavailable;
905
906 case kLoadCompleted:
907 return SvgRootElement(page_.Get()) ? kSizeAvailable : kSizeUnavailable;
908
909 case kDataChangedNotStarted:
910 case kWaitingForAsyncLoadCompletion:
911 CHECK(false);
912 break;
913 }
914
915 NOTREACHED();
916 return kSizeAvailable;
917 }
918
IsSizeAvailable()919 bool SVGImage::IsSizeAvailable() {
920 return SvgRootElement(page_.Get());
921 }
922
FilenameExtension() const923 String SVGImage::FilenameExtension() const {
924 return "svg";
925 }
926
CheckTypeSpecificConditionsForDarkMode(const FloatRect & dest_rect,DarkModeImageClassifier * classifier)927 DarkModeClassification SVGImage::CheckTypeSpecificConditionsForDarkMode(
928 const FloatRect& dest_rect,
929 DarkModeImageClassifier* classifier) {
930 classifier->SetImageType(DarkModeImageClassifier::ImageType::kSvg);
931 return DarkModeClassification::kNotClassified;
932 }
933
934 } // namespace blink
935