blob: b3d0a644387ca40efbf13773c647caac0188de15 [file] [log] [blame]
// Copyright 2017 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/html/canvas/image_element_base.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/loader/image_loader.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
namespace blink {
// static
Image::ImageDecodingMode ImageElementBase::ParseImageDecodingMode(
const AtomicString& async_attr_value) {
if (async_attr_value.IsNull())
return Image::kUnspecifiedDecode;
const auto& value = async_attr_value.LowerASCII();
if (value == "async")
return Image::kAsyncDecode;
if (value == "sync")
return Image::kSyncDecode;
return Image::kUnspecifiedDecode;
}
ImageResourceContent* ImageElementBase::CachedImage() const {
return GetImageLoader().GetContent();
}
const Element& ImageElementBase::GetElement() const {
return *GetImageLoader().GetElement();
}
bool ImageElementBase::IsSVGSource() const {
return CachedImage() && IsA<SVGImage>(CachedImage()->GetImage());
}
bool ImageElementBase::IsImageElement() const {
return CachedImage() && !IsA<SVGImage>(CachedImage()->GetImage());
}
scoped_refptr<Image> ImageElementBase::GetSourceImageForCanvas(
SourceImageStatus* status,
const FloatSize& default_object_size) {
ImageResourceContent* image_content = CachedImage();
if (!GetImageLoader().ImageComplete() || !image_content) {
*status = kIncompleteSourceImageStatus;
return nullptr;
}
if (image_content->ErrorOccurred()) {
*status = kUndecodableSourceImageStatus;
return nullptr;
}
scoped_refptr<Image> source_image = image_content->GetImage();
if (auto* svg_image = DynamicTo<SVGImage>(source_image.get())) {
UseCounter::Count(GetElement().GetDocument(), WebFeature::kSVGInCanvas2D);
FloatSize image_size = svg_image->ConcreteObjectSize(default_object_size);
source_image = SVGImageForContainer::Create(
svg_image, image_size, 1,
GetElement().GetDocument().CompleteURL(GetElement().ImageSourceURL()));
}
*status = kNormalSourceImageStatus;
return source_image->ImageForDefaultFrame();
}
bool ImageElementBase::WouldTaintOrigin() const {
return CachedImage() && !CachedImage()->IsAccessAllowed();
}
FloatSize ImageElementBase::ElementSize(
const FloatSize& default_object_size,
const RespectImageOrientationEnum respect_orientation) const {
ImageResourceContent* image_content = CachedImage();
if (!image_content || !image_content->HasImage())
return FloatSize();
Image* image = image_content->GetImage();
if (auto* svg_image = DynamicTo<SVGImage>(image))
return svg_image->ConcreteObjectSize(default_object_size);
return FloatSize(image->Size(respect_orientation));
}
FloatSize ImageElementBase::DefaultDestinationSize(
const FloatSize& default_object_size,
const RespectImageOrientationEnum respect_orientation) const {
return ElementSize(default_object_size, respect_orientation);
}
bool ImageElementBase::IsAccelerated() const {
return false;
}
const KURL& ImageElementBase::SourceURL() const {
return CachedImage()->GetResponse().CurrentRequestUrl();
}
bool ImageElementBase::IsOpaque() const {
ImageResourceContent* image_content = CachedImage();
if (!GetImageLoader().ImageComplete() || !image_content)
return false;
Image* image = image_content->GetImage();
return image->CurrentFrameKnownToBeOpaque();
}
IntSize ImageElementBase::BitmapSourceSize() const {
ImageResourceContent* image = CachedImage();
if (!image)
return IntSize();
// This method is called by ImageBitmap when creating and cropping the image.
// Return un-oriented size because the cropping must happen before
// orienting.
return image->IntrinsicSize(kDoNotRespectImageOrientation);
}
static bool HasDimensionsForImage(SVGImage* svg_image,
base::Optional<IntRect> crop_rect,
const ImageBitmapOptions* options) {
if (!svg_image->ConcreteObjectSize(FloatSize()).IsEmpty())
return true;
if (crop_rect)
return true;
if (options->hasResizeWidth() && options->hasResizeHeight())
return true;
return false;
}
ScriptPromise ImageElementBase::CreateImageBitmap(
ScriptState* script_state,
base::Optional<IntRect> crop_rect,
const ImageBitmapOptions* options,
ExceptionState& exception_state) {
ImageResourceContent* image_content = CachedImage();
if (!image_content) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"No image can be retrieved from the provided element.");
return ScriptPromise();
}
if (options->hasResizeWidth() && options->resizeWidth() == 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The resize width dimension is equal to 0.");
return ScriptPromise();
}
if (options->hasResizeHeight() && options->resizeHeight() == 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The resize width dimension is equal to 0.");
return ScriptPromise();
}
if (auto* svg_image = DynamicTo<SVGImage>(image_content->GetImage())) {
if (!HasDimensionsForImage(svg_image, crop_rect, options)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The image element contains an SVG image without intrinsic "
"dimensions, and no resize options or crop region are "
"specified.");
return ScriptPromise();
}
// The following function only works on SVGImages (as checked above).
return ImageBitmap::CreateAsync(this, crop_rect, script_state, options);
}
return ImageBitmapSource::FulfillImageBitmap(
script_state, MakeGarbageCollected<ImageBitmap>(this, crop_rect, options),
exception_state);
}
Image::ImageDecodingMode ImageElementBase::GetDecodingModeForPainting(
PaintImage::Id new_id) {
const bool content_transitioned =
last_painted_image_id_ != PaintImage::kInvalidId &&
new_id != PaintImage::kInvalidId && last_painted_image_id_ != new_id;
last_painted_image_id_ = new_id;
// If the image for the element was transitioned, and no preference has been
// specified by the author, prefer sync decoding to avoid flickering the
// element. Async decoding of this image would cause us to display
// intermediate frames with no image while the decode is in progress which
// creates a visual flicker in the transition.
if (content_transitioned &&
decoding_mode_ == Image::ImageDecodingMode::kUnspecifiedDecode)
return Image::ImageDecodingMode::kSyncDecode;
return decoding_mode_;
}
} // namespace blink