| /* |
| * Copyright (C) 2006 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. 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 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 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/svg/graphics/svg_image.h" |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h" |
| #include "third_party/blink/public/platform/web_back_forward_cache_loader_helper.h" |
| #include "third_party/blink/public/platform/web_url.h" |
| #include "third_party/blink/public/platform/web_url_loader.h" |
| #include "third_party/blink/public/platform/web_url_loader_client.h" |
| #include "third_party/blink/public/platform/web_url_loader_factory.h" |
| #include "third_party/blink/public/platform/web_url_request_extra_data.h" |
| #include "third_party/blink/renderer/core/animation/document_animations.h" |
| #include "third_party/blink/renderer/core/animation/document_timeline.h" |
| #include "third_party/blink/renderer/core/dom/document_parser.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" |
| #include "third_party/blink/renderer/core/dom/node_traversal.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/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/layout/intrinsic_sizing_info.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h" |
| #include "third_party/blink/renderer/core/loader/frame_load_request.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/svg/animation/smil_time_container.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h" |
| #include "third_party/blink/renderer/core/svg/svg_animated_preserve_aspect_ratio.h" |
| #include "third_party/blink/renderer/core/svg/svg_fe_image_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_image_element.h" |
| #include "third_party/blink/renderer/core/svg/svg_svg_element.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/graphics/color.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/image_observer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| using TaskRunnerHandle = scheduler::WebResourceLoadingTaskRunnerHandle; |
| |
| class FailingLoader final : public WebURLLoader { |
| public: |
| explicit FailingLoader( |
| std::unique_ptr<TaskRunnerHandle> freezable_task_runner_handle, |
| std::unique_ptr<TaskRunnerHandle> unfreezable_task_runner_handle) |
| : freezable_task_runner_handle_(std::move(freezable_task_runner_handle)), |
| unfreezable_task_runner_handle_( |
| std::move(unfreezable_task_runner_handle)) {} |
| ~FailingLoader() override = default; |
| |
| // WebURLLoader implementation: |
| void LoadSynchronously( |
| std::unique_ptr<network::ResourceRequest> request, |
| scoped_refptr<WebURLRequestExtraData> url_request_extra_data, |
| int requestor_id, |
| 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, |
| std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper> |
| resource_load_info_notifier_wrapper) override { |
| NOTREACHED(); |
| } |
| void LoadAsynchronously( |
| std::unique_ptr<network::ResourceRequest> request, |
| scoped_refptr<WebURLRequestExtraData> url_request_extra_data, |
| int requestor_id, |
| bool no_mime_sniffing, |
| std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper> |
| resource_load_info_notifier_wrapper, |
| WebURLLoaderClient* client) override { |
| NOTREACHED(); |
| } |
| void SetDefersLoading(DeferType) override {} |
| void DidChangePriority(WebURLRequest::Priority, int) override {} |
| scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunnerForBodyLoader() |
| override { |
| return freezable_task_runner_handle_->GetTaskRunner(); |
| } |
| |
| private: |
| const std::unique_ptr<TaskRunnerHandle> freezable_task_runner_handle_; |
| const std::unique_ptr<TaskRunnerHandle> unfreezable_task_runner_handle_; |
| }; |
| |
| class FailingLoaderFactory final : public WebURLLoaderFactory { |
| public: |
| // WebURLLoaderFactory implementation: |
| std::unique_ptr<WebURLLoader> CreateURLLoader( |
| const WebURLRequest&, |
| std::unique_ptr<TaskRunnerHandle> freezable_task_runner_handle, |
| std::unique_ptr<TaskRunnerHandle> unfreezable_task_runner_handle, |
| CrossVariantMojoRemote<blink::mojom::KeepAliveHandleInterfaceBase> |
| keep_alive_handle, |
| WebBackForwardCacheLoaderHelper back_forward_cache_loader_helper) |
| override { |
| return std::make_unique<FailingLoader>( |
| std::move(freezable_task_runner_handle), |
| std::move(unfreezable_task_runner_handle)); |
| } |
| }; |
| |
| } // namespace |
| |
| // SVGImageLocalFrameClient is used to wait until SVG document's load event |
| // in the case where there are subresources asynchronously loaded. |
| // |
| // Reference cycle: SVGImage -(Persistent)-> Page -(Member)-> Frame -(Member)-> |
| // FrameClient == SVGImageLocalFrameClient -(raw)-> SVGImage. |
| class SVGImage::SVGImageLocalFrameClient : public EmptyLocalFrameClient { |
| public: |
| SVGImageLocalFrameClient(SVGImage* image) : image_(image) {} |
| |
| void ClearImage() { image_ = nullptr; } |
| |
| private: |
| std::unique_ptr<WebURLLoaderFactory> CreateURLLoaderFactory() override { |
| // SVG Images have unique security rules that prevent all subresource |
| // requests except for data urls. |
| return std::make_unique<FailingLoaderFactory>(); |
| } |
| |
| void DispatchDidHandleOnloadEvents() override { |
| // The SVGImage was destructed before SVG load completion. |
| if (!image_) |
| return; |
| |
| image_->LoadCompleted(); |
| } |
| |
| // Cleared manually by SVGImage's destructor when |image_| is destructed. |
| SVGImage* image_; |
| }; |
| |
| SVGImage::SVGImage(ImageObserver* observer, bool is_multipart) |
| : Image(observer, is_multipart), |
| paint_controller_(std::make_unique<PaintController>()), |
| // TODO(chikamune): use an existing AgentGroupScheduler |
| // SVG will be shared via MemoryCache (which is renderer process |
| // global cache) across multiple AgentSchedulingGroups. That's |
| // why we can't use an existing AgentSchedulingGroup for now. If |
| // we incorrectly use the existing ASG/AGS and if we freeze task |
| // queues on a AGS, it will affect SVGs on other AGS. To |
| // mitigate this problem, we need to split the MemoryCache into |
| // smaller granularity. There is an active effort to mitigate |
| // this which is called "Memory Cache Per Context" |
| // (https://crbug.com/1127971). |
| agent_group_scheduler_( |
| Thread::MainThread()->Scheduler()->CreateAgentGroupScheduler()), |
| has_pending_timeline_rewind_(false) {} |
| |
| SVGImage::~SVGImage() { |
| AllowDestroyingLayoutObjectInFinalizerScope scope; |
| |
| if (frame_client_) |
| frame_client_->ClearImage(); |
| |
| if (page_) { |
| // It is safe to allow UA events within this scope, because event |
| // dispatching inside the SVG image's document doesn't trigger JavaScript |
| // execution. All script execution is forbidden when an SVG is loaded as an |
| // image subresource - see SetScriptEnabled in SVGImage::DataChanged(). |
| EventDispatchForbiddenScope::AllowUserAgentEvents allow_events; |
| // Store m_page in a local variable, clearing m_page, so that |
| // SVGImageChromeClient knows we're destructed. |
| Page* current_page = page_.Release(); |
| // Break both the loader and view references to the frame |
| current_page->WillBeDestroyed(); |
| } |
| |
| // Verify that page teardown destroyed the Chrome |
| DCHECK(!chrome_client_ || !chrome_client_->GetImage()); |
| } |
| |
| bool SVGImage::IsInSVGImage(const Node* node) { |
| DCHECK(node); |
| |
| Page* page = node->GetDocument().GetPage(); |
| if (!page) |
| return false; |
| |
| return page->GetChromeClient().IsSVGImageChromeClient(); |
| } |
| |
| LocalFrame* SVGImage::GetFrame() const { |
| DCHECK(page_); |
| return To<LocalFrame>(page_->MainFrame()); |
| } |
| |
| SVGSVGElement* SVGImage::RootElement() const { |
| if (!page_) |
| return nullptr; |
| return DynamicTo<SVGSVGElement>(GetFrame()->GetDocument()->documentElement()); |
| } |
| |
| LayoutSVGRoot* SVGImage::LayoutRoot() const { |
| if (SVGSVGElement* root_element = RootElement()) |
| return To<LayoutSVGRoot>(root_element->GetLayoutObject()); |
| return nullptr; |
| } |
| |
| void SVGImage::CheckLoaded() const { |
| CHECK(page_); |
| // Failures of this assertion might result in wrong origin tainting checks, |
| // because CurrentFrameHasSingleSecurityOrigin() assumes all subresources of |
| // the SVG are loaded and thus ready for origin checks. |
| CHECK(GetFrame()->GetDocument()->LoadEventFinished()); |
| } |
| |
| bool SVGImage::CurrentFrameHasSingleSecurityOrigin() const { |
| if (!page_) |
| return true; |
| |
| CheckLoaded(); |
| |
| SVGSVGElement* root_element = RootElement(); |
| if (!root_element) |
| return true; |
| |
| // Don't allow foreignObject elements or images that are not known to be |
| // single-origin since these can leak cross-origin information. |
| for (Node* node = root_element; node; node = FlatTreeTraversal::Next(*node)) { |
| if (IsA<SVGForeignObjectElement>(*node)) |
| return false; |
| if (auto* image = DynamicTo<SVGImageElement>(*node)) { |
| if (!image->CurrentFrameHasSingleSecurityOrigin()) |
| return false; |
| } else if (auto* fe_image = DynamicTo<SVGFEImageElement>(*node)) { |
| if (!fe_image->CurrentFrameHasSingleSecurityOrigin()) |
| return false; |
| } |
| } |
| |
| // Because SVG image rendering disallows external resources and links, these |
| // images effectively are restricted to a single security origin. |
| return true; |
| } |
| |
| IntSize SVGImage::Size() const { |
| return RoundedIntSize(intrinsic_size_); |
| } |
| |
| static float ResolveWidthForRatio(float height, |
| const FloatSize& intrinsic_ratio) { |
| return height * intrinsic_ratio.Width() / intrinsic_ratio.Height(); |
| } |
| |
| static float ResolveHeightForRatio(float width, |
| const FloatSize& intrinsic_ratio) { |
| return width * intrinsic_ratio.Height() / intrinsic_ratio.Width(); |
| } |
| |
| bool SVGImage::HasIntrinsicSizingInfo() const { |
| return LayoutRoot(); |
| } |
| |
| bool SVGImage::GetIntrinsicSizingInfo( |
| IntrinsicSizingInfo& intrinsic_sizing_info) const { |
| const LayoutSVGRoot* layout_root = LayoutRoot(); |
| if (!layout_root) |
| return false; |
| layout_root->UnscaledIntrinsicSizingInfo(intrinsic_sizing_info); |
| return true; |
| } |
| |
| FloatSize SVGImage::ConcreteObjectSize( |
| const FloatSize& default_object_size) const { |
| IntrinsicSizingInfo intrinsic_sizing_info; |
| if (!GetIntrinsicSizingInfo(intrinsic_sizing_info)) |
| return FloatSize(); |
| |
| // https://www.w3.org/TR/css3-images/#default-sizing |
| if (intrinsic_sizing_info.has_width && intrinsic_sizing_info.has_height) |
| return intrinsic_sizing_info.size; |
| |
| // We're not using an intrinsic aspect ratio to resolve a missing |
| // intrinsic width or height when preserveAspectRatio is none. |
| // (Ref: crbug.com/584172) |
| SVGSVGElement* svg = RootElement(); |
| if (svg->preserveAspectRatio()->CurrentValue()->Align() == |
| SVGPreserveAspectRatio::kSvgPreserveaspectratioNone) |
| return default_object_size; |
| |
| if (intrinsic_sizing_info.has_width) { |
| if (intrinsic_sizing_info.aspect_ratio.IsEmpty()) |
| return FloatSize(intrinsic_sizing_info.size.Width(), |
| default_object_size.Height()); |
| |
| return FloatSize(intrinsic_sizing_info.size.Width(), |
| ResolveHeightForRatio(intrinsic_sizing_info.size.Width(), |
| intrinsic_sizing_info.aspect_ratio)); |
| } |
| |
| if (intrinsic_sizing_info.has_height) { |
| if (intrinsic_sizing_info.aspect_ratio.IsEmpty()) |
| return FloatSize(default_object_size.Width(), |
| intrinsic_sizing_info.size.Height()); |
| |
| return FloatSize(ResolveWidthForRatio(intrinsic_sizing_info.size.Height(), |
| intrinsic_sizing_info.aspect_ratio), |
| intrinsic_sizing_info.size.Height()); |
| } |
| |
| if (!intrinsic_sizing_info.aspect_ratio.IsEmpty()) { |
| // "A contain constraint is resolved by setting the concrete object size to |
| // the largest rectangle that has the object's intrinsic aspect ratio and |
| // additionally has neither width nor height larger than the constraint |
| // rectangle's width and height, respectively." |
| float solution_width = ResolveWidthForRatio( |
| default_object_size.Height(), intrinsic_sizing_info.aspect_ratio); |
| if (solution_width <= default_object_size.Width()) |
| return FloatSize(solution_width, default_object_size.Height()); |
| |
| float solution_height = ResolveHeightForRatio( |
| default_object_size.Width(), intrinsic_sizing_info.aspect_ratio); |
| return FloatSize(default_object_size.Width(), solution_height); |
| } |
| |
| return default_object_size; |
| } |
| |
| SVGImage::DrawInfo::DrawInfo(const FloatSize& container_size, |
| float zoom, |
| const KURL& url) |
| : container_size_(container_size), |
| rounded_container_size_(RoundedLayoutSize(container_size)), |
| zoom_(zoom), |
| url_(url) {} |
| |
| FloatSize SVGImage::DrawInfo::CalculateResidualScale() const { |
| return FloatSize(rounded_container_size_.Width() / container_size_.Width(), |
| rounded_container_size_.Height() / container_size_.Height()); |
| } |
| |
| void SVGImage::DrawForContainer(const DrawInfo& draw_info, |
| cc::PaintCanvas* canvas, |
| const PaintFlags& flags, |
| const FloatRect& dst_rect, |
| const FloatRect& src_rect) { |
| FloatRect unzoomed_src = src_rect; |
| unzoomed_src.Scale(1 / draw_info.Zoom()); |
| |
| // Compensate for the container size rounding by adjusting the source rect. |
| FloatSize residual_scale = draw_info.CalculateResidualScale(); |
| unzoomed_src.SetSize(unzoomed_src.Size().ScaledBy(residual_scale.Width(), |
| residual_scale.Height())); |
| |
| DrawInternal(draw_info, canvas, flags, dst_rect, unzoomed_src); |
| } |
| |
| PaintImage SVGImage::PaintImageForCurrentFrame() { |
| const DrawInfo draw_info(FloatSize(intrinsic_size_), 1, NullURL()); |
| auto builder = CreatePaintImageBuilder(); |
| PopulatePaintRecordForCurrentFrameForContainer(draw_info, builder); |
| return builder.TakePaintImage(); |
| } |
| |
| void SVGImage::DrawPatternForContainer(const DrawInfo& draw_info, |
| GraphicsContext& context, |
| const FloatRect& src_rect, |
| const FloatSize& tile_scale, |
| const FloatPoint& phase, |
| SkBlendMode composite_op, |
| const FloatRect& dst_rect, |
| const FloatSize& repeat_spacing) { |
| // Tile adjusted for scaling/stretch. |
| FloatRect tile(src_rect); |
| tile.Scale(tile_scale.Width(), tile_scale.Height()); |
| |
| // Expand the tile to account for repeat spacing. |
| FloatRect spaced_tile(tile); |
| spaced_tile.Expand(FloatSize(repeat_spacing)); |
| |
| SkMatrix pattern_transform; |
| pattern_transform.setTranslate(phase.X() + spaced_tile.X(), |
| phase.Y() + spaced_tile.Y()); |
| |
| PaintRecordBuilder builder(context); |
| { |
| DrawingRecorder recorder(builder.Context(), builder, |
| DisplayItem::Type::kSVGImage); |
| // When generating an expanded tile, make sure we don't draw into the |
| // spacing area. |
| if (tile != spaced_tile) |
| builder.Context().Clip(tile); |
| DrawForContainer(draw_info, builder.Context().Canvas(), PaintFlags(), tile, |
| src_rect); |
| } |
| |
| PaintFlags flags; |
| flags.setShader(PaintShader::MakePaintRecord( |
| builder.EndRecording(), spaced_tile, SkTileMode::kRepeat, |
| SkTileMode::kRepeat, &pattern_transform)); |
| // If the shader could not be instantiated (e.g. non-invertible matrix), |
| // draw transparent. |
| // Note: we can't simply bail, because of arbitrary blend mode. |
| if (!flags.HasShader()) |
| flags.setColor(SK_ColorTRANSPARENT); |
| |
| flags.setBlendMode(composite_op); |
| flags.setColorFilter(sk_ref_sp(context.GetColorFilter())); |
| context.DrawRect(dst_rect, flags); |
| |
| StartAnimation(); |
| } |
| |
| void SVGImage::PopulatePaintRecordForCurrentFrameForContainer( |
| const DrawInfo& draw_info, |
| PaintImageBuilder& builder) { |
| PaintRecorder recorder; |
| const FloatSize size(draw_info.ContainerSize().ScaledBy(draw_info.Zoom())); |
| const IntRect dest_rect(IntPoint(), RoundedIntSize(size)); |
| cc::PaintCanvas* canvas = recorder.beginRecording(dest_rect); |
| DrawForContainer(draw_info, canvas, PaintFlags(), FloatRect(dest_rect), |
| FloatRect(FloatPoint(), size)); |
| builder.set_paint_record(recorder.finishRecordingAsPicture(), dest_rect, |
| PaintImage::GetNextContentId()); |
| |
| builder.set_completion_state( |
| load_state_ == LoadState::kLoadCompleted |
| ? PaintImage::CompletionState::DONE |
| : PaintImage::CompletionState::PARTIALLY_DONE); |
| } |
| |
| bool SVGImage::ApplyShaderInternal(const DrawInfo& draw_info, |
| PaintFlags& flags, |
| const SkMatrix& local_matrix) { |
| if (draw_info.ContainerSize().IsEmpty()) |
| return false; |
| sk_sp<PaintRecord> record = PaintRecordForCurrentFrame(draw_info); |
| if (!record) |
| return false; |
| |
| const FloatRect bounds(FloatPoint(), draw_info.ContainerSize()); |
| flags.setShader(PaintShader::MakePaintRecord( |
| std::move(record), bounds, SkTileMode::kRepeat, SkTileMode::kRepeat, |
| &local_matrix)); |
| |
| // Animation is normally refreshed in Draw() impls, which we don't reach when |
| // painting via shaders. |
| StartAnimation(); |
| return true; |
| } |
| |
| bool SVGImage::ApplyShader(PaintFlags& flags, const SkMatrix& local_matrix) { |
| const DrawInfo draw_info(FloatSize(intrinsic_size_), 1, NullURL()); |
| return ApplyShaderInternal(draw_info, flags, local_matrix); |
| } |
| |
| bool SVGImage::ApplyShaderForContainer(const DrawInfo& draw_info, |
| PaintFlags& flags, |
| const SkMatrix& local_matrix) { |
| // Compensate for the container size rounding. |
| FloatSize residual_scale = |
| draw_info.CalculateResidualScale().ScaledBy(draw_info.Zoom()); |
| auto adjusted_local_matrix = local_matrix; |
| adjusted_local_matrix.preScale(residual_scale.Width(), |
| residual_scale.Height()); |
| return ApplyShaderInternal(draw_info, flags, adjusted_local_matrix); |
| } |
| |
| void SVGImage::Draw(cc::PaintCanvas* canvas, |
| const PaintFlags& flags, |
| const FloatRect& dst_rect, |
| const FloatRect& src_rect, |
| const SkSamplingOptions&, |
| RespectImageOrientationEnum, |
| ImageClampingMode, |
| ImageDecodingMode) { |
| const DrawInfo draw_info(FloatSize(intrinsic_size_), 1, NullURL()); |
| DrawInternal(draw_info, canvas, flags, dst_rect, src_rect); |
| } |
| |
| sk_sp<PaintRecord> SVGImage::PaintRecordForCurrentFrame( |
| const DrawInfo& draw_info) { |
| if (!page_) |
| return nullptr; |
| // Temporarily disable the image observer to prevent ChangeInRect() calls due |
| // re-laying out the image. |
| ImageObserverDisabler disable_image_observer(this); |
| |
| const LayoutSize layout_container_size = draw_info.RoundedContainerSize(); |
| if (LayoutSVGRoot* layout_root = LayoutRoot()) |
| layout_root->SetContainerSize(layout_container_size); |
| LocalFrameView* view = GetFrame()->View(); |
| const IntSize rounded_container_size = RoundedIntSize(layout_container_size); |
| view->Resize(rounded_container_size); |
| page_->GetVisualViewport().SetSize(rounded_container_size); |
| |
| // Always call processUrlFragment, even if the url is empty, because |
| // there may have been a previous url/fragment that needs to be reset. |
| view->ProcessUrlFragment(draw_info.Url(), /*same_document_navigation=*/false); |
| |
| // If the image was reset, we need to rewind the timeline back to 0. This |
| // needs to be done before painting, or else we wouldn't get the correct |
| // reset semantics (we'd paint the "last" frame rather than the one at |
| // time=0.) The reason we do this here and not in resetAnimation() is to |
| // avoid setting timers from the latter. |
| FlushPendingTimelineRewind(); |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| view->UpdateAllLifecyclePhases(DocumentUpdateReason::kSVGImage); |
| return view->GetPaintRecord(); |
| } |
| |
| view->UpdateAllLifecyclePhasesExceptPaint(DocumentUpdateReason::kSVGImage); |
| PaintRecordBuilder builder(*paint_controller_); |
| view->PaintOutsideOfLifecycle(builder.Context(), kGlobalPaintNormalPhase); |
| return builder.EndRecording(); |
| } |
| |
| static bool DrawNeedsLayer(const PaintFlags& flags) { |
| if (SkColorGetA(flags.getColor()) < 255) |
| return true; |
| |
| // This is needed to preserve the dark mode filter that |
| // has been set in GraphicsContext. |
| if (flags.getColorFilter()) |
| return true; |
| |
| return flags.getBlendMode() != SkBlendMode::kSrcOver; |
| } |
| |
| void SVGImage::DrawInternal(const DrawInfo& draw_info, |
| cc::PaintCanvas* canvas, |
| const PaintFlags& flags, |
| const FloatRect& dst_rect, |
| const FloatRect& unzoomed_src_rect) { |
| sk_sp<PaintRecord> record = PaintRecordForCurrentFrame(draw_info); |
| if (!record) |
| return; |
| |
| { |
| PaintCanvasAutoRestore ar(canvas, false); |
| if (DrawNeedsLayer(flags)) { |
| SkRect layer_rect = dst_rect; |
| canvas->saveLayer(&layer_rect, &flags); |
| } |
| // We can only draw the entire frame, clipped to the rect we want. So |
| // compute where the top left of the image would be if we were drawing |
| // without clipping, and translate accordingly. |
| canvas->save(); |
| canvas->clipRect(EnclosingIntRect(dst_rect)); |
| canvas->concat(SkMatrix::RectToRect(unzoomed_src_rect, dst_rect)); |
| canvas->drawPicture(std::move(record)); |
| canvas->restore(); |
| } |
| |
| // Start any (SMIL) animations if needed. This will restart or continue |
| // animations if preceded by calls to resetAnimation or stopAnimation |
| // respectively. |
| StartAnimation(); |
| } |
| |
| void SVGImage::ScheduleTimelineRewind() { |
| has_pending_timeline_rewind_ = true; |
| } |
| |
| void SVGImage::FlushPendingTimelineRewind() { |
| if (!has_pending_timeline_rewind_) |
| return; |
| if (SVGSVGElement* root_element = RootElement()) |
| root_element->setCurrentTime(0); |
| has_pending_timeline_rewind_ = false; |
| } |
| |
| void SVGImage::StartAnimation() { |
| SVGSVGElement* root_element = RootElement(); |
| if (!root_element) |
| return; |
| chrome_client_->ResumeAnimation(); |
| if (root_element->animationsPaused()) |
| root_element->unpauseAnimations(); |
| } |
| |
| void SVGImage::StopAnimation() { |
| SVGSVGElement* root_element = RootElement(); |
| if (!root_element) |
| return; |
| chrome_client_->SuspendAnimation(); |
| root_element->pauseAnimations(); |
| } |
| |
| void SVGImage::ResetAnimation() { |
| SVGSVGElement* root_element = RootElement(); |
| if (!root_element) |
| return; |
| chrome_client_->SuspendAnimation(); |
| root_element->pauseAnimations(); |
| ScheduleTimelineRewind(); |
| } |
| |
| void SVGImage::RestoreAnimation() { |
| // If the image has no animations then do nothing. |
| if (!MaybeAnimated()) |
| return; |
| // If there are no clients, or no client is going to render, then do nothing. |
| ImageObserver* image_observer = GetImageObserver(); |
| if (!image_observer || image_observer->ShouldPauseAnimation(this)) |
| return; |
| StartAnimation(); |
| } |
| |
| bool SVGImage::MaybeAnimated() { |
| SVGSVGElement* root_element = RootElement(); |
| if (!root_element) |
| return false; |
| return root_element->TimeContainer()->HasAnimations() || |
| root_element->GetDocument().Timeline().HasPendingUpdates(); |
| } |
| |
| void SVGImage::ServiceAnimations( |
| base::TimeTicks monotonic_animation_start_time) { |
| if (!GetImageObserver()) |
| return; |
| |
| // If none of our observers (sic!) are visible, or for some other reason |
| // does not want us to keep running animations, stop them until further |
| // notice (next paint.) |
| if (GetImageObserver()->ShouldPauseAnimation(this)) { |
| StopAnimation(); |
| return; |
| } |
| |
| // serviceScriptedAnimations runs requestAnimationFrame callbacks, but SVG |
| // images can't have any so we assert there's no script. |
| ScriptForbiddenScope forbid_script; |
| |
| // The calls below may trigger GCs, so set up the required persistent |
| // reference on the ImageResourceContent which owns this SVGImage. By |
| // transitivity, that will keep the associated SVGImageChromeClient object |
| // alive. |
| Persistent<ImageObserver> protect(GetImageObserver()); |
| page_->Animator().ServiceScriptedAnimations(monotonic_animation_start_time); |
| |
| // Do *not* update the paint phase. It's critical to paint only when |
| // actually generating painted output, not only for performance reasons, |
| // but to preserve correct coherence of the cache of the output with |
| // the needsRepaint bits of the PaintLayers in the image. |
| LocalFrame* frame = GetFrame(); |
| LocalFrameView* frame_view = frame->View(); |
| frame_view->UpdateAllLifecyclePhasesExceptPaint( |
| DocumentUpdateReason::kSVGImage); |
| |
| // We run UpdateAnimations after the paint phase, but per the above comment, |
| // we don't want to run lifecycle through to paint for SVG images. Since we |
| // know SVG images never have composited animations, we can update animations |
| // directly without worrying about including PaintArtifactCompositor's |
| // analysis of whether animations should be composited. |
| frame->GetDocument()->GetDocumentAnimations().UpdateAnimations( |
| DocumentLifecycle::kLayoutClean, nullptr); |
| } |
| |
| void SVGImage::AdvanceAnimationForTesting() { |
| if (SVGSVGElement* root_element = RootElement()) { |
| root_element->TimeContainer()->AdvanceFrameForTesting(); |
| |
| // The following triggers animation updates which can issue a new draw |
| // and temporarily change the animation timeline. It's necessary to call |
| // reset before changing to a time value as animation clock does not |
| // expect to go backwards. |
| base::TimeTicks current_animation_time = |
| page_->Animator().Clock().CurrentTime(); |
| page_->Animator().Clock().ResetTimeForTesting(); |
| if (root_element->TimeContainer()->IsStarted()) |
| root_element->TimeContainer()->ResetDocumentTime(); |
| page_->Animator().ServiceScriptedAnimations( |
| root_element->GetDocument().Timeline().CalculateZeroTime() + |
| base::TimeDelta::FromSecondsD(root_element->getCurrentTime())); |
| GetImageObserver()->Changed(this); |
| page_->Animator().Clock().ResetTimeForTesting(); |
| page_->Animator().Clock().UpdateTime(current_animation_time); |
| } |
| } |
| |
| SVGImageChromeClient& SVGImage::ChromeClientForTesting() { |
| return *chrome_client_; |
| } |
| |
| void SVGImage::UpdateUseCounters(const Document& document) const { |
| if (SVGSVGElement* root_element = RootElement()) { |
| if (root_element->TimeContainer()->HasAnimations()) { |
| document.CountUse(WebFeature::kSVGSMILAnimationInImageRegardlessOfCache); |
| } |
| } |
| } |
| |
| void SVGImage::LoadCompleted() { |
| switch (load_state_) { |
| case kInDataChanged: |
| load_state_ = kLoadCompleted; |
| break; |
| |
| case kWaitingForAsyncLoadCompletion: |
| load_state_ = kLoadCompleted; |
| |
| // Because LoadCompleted() is called synchronously from |
| // Document::ImplicitClose(), we defer AsyncLoadCompleted() to avoid |
| // potential bugs and timing dependencies around ImplicitClose() and |
| // to make LoadEventFinished() true when AsyncLoadCompleted() is called. |
| GetFrame() |
| ->GetTaskRunner(TaskType::kInternalLoading) |
| ->PostTask(FROM_HERE, WTF::Bind(&SVGImage::NotifyAsyncLoadCompleted, |
| scoped_refptr<SVGImage>(this))); |
| break; |
| |
| case kDataChangedNotStarted: |
| case kLoadCompleted: |
| CHECK(false); |
| break; |
| } |
| } |
| |
| void SVGImage::NotifyAsyncLoadCompleted() { |
| if (GetImageObserver()) |
| GetImageObserver()->AsyncLoadCompleted(this); |
| } |
| |
| Image::SizeAvailability SVGImage::DataChanged(bool all_data_received) { |
| TRACE_EVENT0("blink", "SVGImage::dataChanged"); |
| |
| // Don't do anything if is an empty image. |
| if (!Data()->size()) |
| return kSizeAvailable; |
| |
| if (!all_data_received) |
| return page_ ? kSizeAvailable : kSizeUnavailable; |
| |
| CHECK(!page_); |
| |
| // SVGImage will fire events (and the default C++ handlers run) but doesn't |
| // actually allow script to run so it's fine to call into it. We allow this |
| // since it means an SVG data url can synchronously load like other image |
| // types. |
| EventDispatchForbiddenScope::AllowUserAgentEvents allow_user_agent_events; |
| |
| CHECK_EQ(load_state_, kDataChangedNotStarted); |
| load_state_ = kInDataChanged; |
| |
| Page::PageClients page_clients; |
| FillWithEmptyClients(page_clients); |
| chrome_client_ = MakeGarbageCollected<SVGImageChromeClient>(this); |
| page_clients.chrome_client = chrome_client_.Get(); |
| |
| // FIXME: If this SVG ends up loading itself, we might leak the world. |
| // The Cache code does not know about ImageResources holding Frames and |
| // won't know to break the cycle. |
| // This will become an issue when SVGImage will be able to load other |
| // SVGImage objects, but we're safe now, because SVGImage can only be |
| // loaded by a top-level document. |
| Page* page; |
| { |
| TRACE_EVENT0("blink", "SVGImage::dataChanged::createPage"); |
| page = Page::CreateNonOrdinary(page_clients, *agent_group_scheduler_); |
| page->GetSettings().SetScriptEnabled(false); |
| page->GetSettings().SetPluginsEnabled(false); |
| |
| // Because this page is detached, it can't get default font settings |
| // from the embedder. Copy over font settings so we have sensible |
| // defaults. These settings are fixed and will not update if changed. |
| if (!Page::OrdinaryPages().IsEmpty()) { |
| Settings& default_settings = |
| (*Page::OrdinaryPages().begin())->GetSettings(); |
| page->GetSettings().GetGenericFontFamilySettings() = |
| default_settings.GetGenericFontFamilySettings(); |
| page->GetSettings().SetMinimumFontSize( |
| default_settings.GetMinimumFontSize()); |
| page->GetSettings().SetMinimumLogicalFontSize( |
| default_settings.GetMinimumLogicalFontSize()); |
| page->GetSettings().SetDefaultFontSize( |
| default_settings.GetDefaultFontSize()); |
| page->GetSettings().SetDefaultFixedFontSize( |
| default_settings.GetDefaultFixedFontSize()); |
| |
| // Also copy the preferred-color-scheme to ensure a responsiveness to |
| // dark/light color schemes. |
| page->GetSettings().SetPreferredColorScheme( |
| default_settings.GetPreferredColorScheme()); |
| } |
| chrome_client_->InitAnimationTimer(page->GetPageScheduler() |
| ->GetAgentGroupScheduler() |
| .CompositorTaskRunner()); |
| } |
| |
| LocalFrame* frame = nullptr; |
| { |
| TRACE_EVENT0("blink", "SVGImage::dataChanged::createFrame"); |
| DCHECK(!frame_client_); |
| frame_client_ = MakeGarbageCollected<SVGImageLocalFrameClient>(this); |
| frame = MakeGarbageCollected<LocalFrame>( |
| frame_client_, *page, nullptr, nullptr, nullptr, |
| FrameInsertType::kInsertInConstructor, LocalFrameToken(), nullptr, |
| nullptr, /* policy_container */ nullptr); |
| frame->SetView(MakeGarbageCollected<LocalFrameView>(*frame)); |
| frame->Init(nullptr); |
| } |
| |
| // SVG Images will always synthesize a viewBox, if it's not available, and |
| // thus never see scrollbars. |
| frame->View()->SetCanHaveScrollbars(false); |
| // SVG Images are transparent. |
| frame->View()->SetBaseBackgroundColor(Color::kTransparent); |
| |
| page_ = page; |
| |
| TRACE_EVENT0("blink", "SVGImage::dataChanged::load"); |
| |
| frame->ForceSynchronousDocumentInstall("image/svg+xml", Data()); |
| |
| // Intrinsic sizing relies on computed style (e.g. font-size and |
| // writing-mode). |
| frame->GetDocument()->UpdateStyleAndLayoutTree(); |
| |
| // Set the concrete object size before a container size is available. |
| intrinsic_size_ = RoundedLayoutSize(ConcreteObjectSize(FloatSize( |
| LayoutReplaced::kDefaultWidth, LayoutReplaced::kDefaultHeight))); |
| |
| DCHECK(page_); |
| switch (load_state_) { |
| case kInDataChanged: |
| load_state_ = kWaitingForAsyncLoadCompletion; |
| return RootElement() ? kSizeAvailableAndLoadingAsynchronously |
| : kSizeUnavailable; |
| |
| case kLoadCompleted: |
| return RootElement() ? kSizeAvailable : kSizeUnavailable; |
| |
| case kDataChangedNotStarted: |
| case kWaitingForAsyncLoadCompletion: |
| CHECK(false); |
| break; |
| } |
| |
| NOTREACHED(); |
| return kSizeAvailable; |
| } |
| |
| bool SVGImage::IsSizeAvailable() { |
| return RootElement(); |
| } |
| |
| String SVGImage::FilenameExtension() const { |
| return "svg"; |
| } |
| |
| } // namespace blink |