blob: 53d78ef18261effa0f68376f990e6297b288d1e8 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/paint/image_painter.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
#include "third_party/blink/public/mojom/feature_policy/policy_value.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/editing/frame_selection.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/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_replaced.h"
#include "third_party/blink/renderer/core/layout/text_run_constructor.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/box_painter.h"
#include "third_party/blink/renderer/core/paint/image_element_timing.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
#include "third_party/blink/renderer/core/paint/scoped_paint_state.h"
#include "third_party/blink/renderer/platform/geometry/layout_point.h"
#include "third_party/blink/renderer/platform/graphics/paint/display_item_cache_skipper.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/path.h"
#include "third_party/blink/renderer/platform/graphics/placeholder_image.h"
#include "third_party/blink/renderer/platform/graphics/scoped_interpolation_quality.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
// TODO(loonybear): Currently oversized-images policy is only reinforced on
// HTMLImageElement. Use data from |layout_image|, |content_rect| and/or
// Document to support this policy on other image types (crbug.com/930281).
bool CheckForOversizedImagesPolicy(const LayoutImage& layout_image,
scoped_refptr<Image> image) {
DCHECK(image);
if (!RuntimeEnabledFeatures::ExperimentalPoliciesEnabled(
layout_image.GetDocument().GetExecutionContext()))
return false;
DoubleSize layout_size(layout_image.ContentSize());
IntSize image_size = image->Size();
if (layout_size.IsEmpty() || image_size.IsEmpty())
return false;
// Note: Do not use frame->GetDevicePixelRatio() here, because it
// leads to different behaviour on MacOS platform. https://crbug.com/716231.
// virtual/scalefactor200/http/tests/images/document-policy/document-policy-oversized-images-edge-cases.html
// verifies the behaviour.
const double dsf =
layout_image.GetDocument().GetPage()->DeviceScaleFactorDeprecated();
const double downscale_ratio_width =
image_size.Width() / layout_size.Width() / dsf;
const double downscale_ratio_height =
image_size.Height() / layout_size.Height() / dsf;
const LayoutImageResource* image_resource = layout_image.ImageResource();
const ImageResourceContent* cached_image =
image_resource ? image_resource->CachedImage() : nullptr;
const String& image_url =
cached_image ? cached_image->Url().GetString() : g_empty_string;
return !layout_image.GetDocument().domWindow()->IsFeatureEnabled(
mojom::blink::DocumentPolicyFeature::kOversizedImages,
blink::PolicyValue::CreateDecDouble(
std::max(downscale_ratio_width, downscale_ratio_height)),
ReportOptions::kReportOnFailure, g_empty_string, image_url);
}
} // namespace
void ImagePainter::Paint(const PaintInfo& paint_info) {
layout_image_.LayoutReplaced::Paint(paint_info);
if (paint_info.phase == PaintPhase::kOutline)
PaintAreaElementFocusRing(paint_info);
}
void ImagePainter::PaintAreaElementFocusRing(const PaintInfo& paint_info) {
Document& document = layout_image_.GetDocument();
if (document.Printing() ||
!document.GetFrame()->Selection().FrameIsFocusedAndActive())
return;
auto* area_element = DynamicTo<HTMLAreaElement>(document.FocusedElement());
if (!area_element)
return;
if (area_element->ImageElement() != layout_image_.GetNode())
return;
// We use EnsureComputedStyle() instead of GetComputedStyle() here because
// <area> is used and its style applied even if it has display:none.
const ComputedStyle* area_element_style = area_element->EnsureComputedStyle();
// If the outline width is 0 we want to avoid drawing anything even if we
// don't use the value directly.
if (!area_element_style->OutlineWidth())
return;
Path path = area_element->GetPath(&layout_image_);
if (path.IsEmpty())
return;
ScopedPaintState paint_state(layout_image_, paint_info);
auto paint_offset = paint_state.PaintOffset();
path.Translate(FloatSize(paint_offset));
if (DrawingRecorder::UseCachedDrawingIfPossible(
paint_info.context, layout_image_, DisplayItem::kImageAreaFocusRing))
return;
BoxDrawingRecorder recorder(paint_info.context, layout_image_,
DisplayItem::kImageAreaFocusRing, paint_offset);
// FIXME: Clip path instead of context when Skia pathops is ready.
// https://crbug.com/251206
paint_info.context.Save();
PhysicalRect focus_rect = layout_image_.PhysicalContentBoxRect();
focus_rect.Move(paint_offset);
paint_info.context.Clip(PixelSnappedIntRect(focus_rect));
paint_info.context.DrawFocusRing(
path, area_element_style->GetOutlineStrokeWidthForFocusRing(),
area_element_style->OutlineOffsetInt(),
layout_image_.ResolveColor(*area_element_style,
GetCSSPropertyOutlineColor()));
paint_info.context.Restore();
}
void ImagePainter::PaintReplaced(const PaintInfo& paint_info,
const PhysicalOffset& paint_offset) {
LayoutSize content_size = layout_image_.ContentSize();
bool has_image = layout_image_.ImageResource()->HasImage();
if (has_image) {
if (content_size.IsEmpty())
return;
} else {
if (paint_info.phase == PaintPhase::kSelectionDragImage)
return;
if (content_size.Width() <= 2 || content_size.Height() <= 2)
return;
}
GraphicsContext& context = paint_info.context;
if (DrawingRecorder::UseCachedDrawingIfPossible(context, layout_image_,
paint_info.phase))
return;
// Disable cache in under-invalidation checking mode for animated image
// because it may change before it's actually invalidated.
base::Optional<DisplayItemCacheSkipper> cache_skipper;
if (RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() &&
layout_image_.ImageResource() &&
layout_image_.ImageResource()->MaybeAnimated())
cache_skipper.emplace(context);
PhysicalRect content_rect(
paint_offset + layout_image_.PhysicalContentBoxOffset(),
PhysicalSizeToBeNoop(content_size));
if (!has_image) {
// Draw an outline rect where the image should be.
IntRect paint_rect = PixelSnappedIntRect(content_rect);
BoxDrawingRecorder recorder(context, layout_image_, paint_info.phase,
paint_offset);
context.SetStrokeStyle(kSolidStroke);
context.SetStrokeColor(Color::kLightGray);
context.SetFillColor(Color::kTransparent);
context.DrawRect(paint_rect);
return;
}
PhysicalRect paint_rect = layout_image_.ReplacedContentRect();
paint_rect.offset += paint_offset;
BoxDrawingRecorder recorder(context, layout_image_, paint_info.phase,
paint_offset);
DCHECK(paint_info.PaintContainer());
PaintIntoRect(context, paint_rect, content_rect);
}
void ImagePainter::PaintIntoRect(GraphicsContext& context,
const PhysicalRect& dest_rect,
const PhysicalRect& content_rect) {
const LayoutImageResource& image_resource = *layout_image_.ImageResource();
if (!image_resource.HasImage() || image_resource.ErrorOccurred())
return; // FIXME: should we just ASSERT these conditions? (audit all
// callers).
IntRect pixel_snapped_dest_rect = PixelSnappedIntRect(dest_rect);
if (pixel_snapped_dest_rect.IsEmpty())
return;
scoped_refptr<Image> image =
image_resource.GetImage(FloatSize(dest_rect.size));
if (!image || image->IsNull())
return;
// Do not respect the image orientation when computing the source rect. It is
// in the un-orientated dimensions.
FloatRect src_rect(FloatPoint(),
image->SizeAsFloat(kDoNotRespectImageOrientation));
// If the content rect requires clipping, adjust |srcRect| and
// |pixelSnappedDestRect| over using a clip.
if (!content_rect.Contains(dest_rect)) {
IntRect pixel_snapped_content_rect = PixelSnappedIntRect(content_rect);
pixel_snapped_content_rect.Intersect(pixel_snapped_dest_rect);
if (pixel_snapped_content_rect.IsEmpty())
return;
src_rect = MapRect(FloatRect(pixel_snapped_content_rect),
FloatRect(pixel_snapped_dest_rect), src_rect);
pixel_snapped_dest_rect = pixel_snapped_content_rect;
}
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
"data",
inspector_paint_image_event::Data(layout_image_, src_rect,
FloatRect(dest_rect)));
ScopedInterpolationQuality interpolation_quality_scope(
context, layout_image_.StyleRef().GetInterpolationQuality());
Node* node = layout_image_.GetNode();
auto* image_element = DynamicTo<HTMLImageElement>(node);
Image::ImageDecodingMode decode_mode =
image_element
? image_element->GetDecodingModeForPainting(image->paint_image_id())
: Image::kUnspecifiedDecode;
// TODO(loonybear): Support image policies on other image types in addition to
// HTMLImageElement.
if (image_element) {
if (CheckForOversizedImagesPolicy(layout_image_, image) ||
image_element->IsImagePolicyViolated()) {
// Does not set an observer for the placeholder image, setting it to null.
scoped_refptr<PlaceholderImage> placeholder_image =
PlaceholderImage::Create(nullptr, image->Size(),
image->Data() ? image->Data()->size() : 0);
placeholder_image->SetIconAndTextScaleFactor(
layout_image_.GetFrame()->PageZoomFactor());
image = std::move(placeholder_image);
}
}
context.DrawImage(image.get(), decode_mode,
FloatRect(pixel_snapped_dest_rect), &src_rect,
layout_image_.StyleRef().HasFilterInducingProperty(),
SkBlendMode::kSrcOver, image_resource.ImageOrientation());
if (ImageResourceContent* image_content = image_resource.CachedImage()) {
if ((IsA<HTMLImageElement>(node) || IsA<HTMLVideoElement>(node)) &&
image_content->IsLoaded()) {
LocalDOMWindow* window = layout_image_.GetDocument().domWindow();
DCHECK(window);
ImageElementTiming::From(*window).NotifyImagePainted(
layout_image_, *image_content,
context.GetPaintController().CurrentPaintChunkProperties(),
pixel_snapped_dest_rect);
}
PaintTimingDetector::NotifyImagePaint(
layout_image_, image->Size(), *image_content,
context.GetPaintController().CurrentPaintChunkProperties(),
pixel_snapped_dest_rect);
}
}
} // namespace blink