blob: fe943479ebb228b5deb788164a9337723853d5d7 [file] [log] [blame]
// Copyright 2020 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/modules/webcodecs/image_decoder_external.h"
#include "media/media_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_image_decode_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_image_decoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_image_frame.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_image_track.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
class ImageDecoderTest : public testing::Test {
protected:
ImageDecoderExternal* CreateDecoder(V8TestingScope* v8_scope,
const char* file_name,
const char* mime_type) {
auto* init = MakeGarbageCollected<ImageDecoderInit>();
init->setType(mime_type);
auto data = ReadFile(file_name);
DCHECK(!data->IsEmpty()) << "Missing file: " << file_name;
init->setData(ArrayBufferOrArrayBufferViewOrReadableStream::FromArrayBuffer(
DOMArrayBuffer::Create(std::move(data))));
return ImageDecoderExternal::Create(v8_scope->GetScriptState(), init,
v8_scope->GetExceptionState());
}
ImageFrameExternal* ToImageFrame(V8TestingScope* v8_scope,
ScriptValue value) {
return NativeValueTraits<ImageFrameExternal>::NativeValue(
v8_scope->GetIsolate(), value.V8Value(), v8_scope->GetExceptionState());
}
ImageDecodeOptions* MakeOptions(uint32_t frame_index = 0,
bool complete_frames_only = true) {
auto* options = MakeGarbageCollected<ImageDecodeOptions>();
options->setFrameIndex(frame_index);
options->setCompleteFramesOnly(complete_frames_only);
return options;
}
scoped_refptr<SharedBuffer> ReadFile(StringView file_name) {
StringBuilder file_path;
file_path.Append(test::BlinkWebTestsDir());
file_path.Append('/');
file_path.Append(file_name);
return test::ReadFromFile(file_path.ToString());
}
};
TEST_F(ImageDecoderTest, CanDecodeType) {
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/jpeg"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/pjpeg"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/jpg"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/png"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/x-png"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/apng"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/gif"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/webp"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/x-icon"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/vnd.microsoft.icon"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/bmp"));
EXPECT_TRUE(ImageDecoderExternal::canDecodeType("image/x-xbitmap"));
EXPECT_EQ(ImageDecoderExternal::canDecodeType("image/avif"),
BUILDFLAG(ENABLE_AV1_DECODER));
EXPECT_FALSE(ImageDecoderExternal::canDecodeType("image/svg+xml"));
EXPECT_FALSE(ImageDecoderExternal::canDecodeType("image/heif"));
EXPECT_FALSE(ImageDecoderExternal::canDecodeType("image/pcx"));
EXPECT_FALSE(ImageDecoderExternal::canDecodeType("image/bpg"));
}
TEST_F(ImageDecoderTest, DecodeEmpty) {
V8TestingScope v8_scope;
auto* init = MakeGarbageCollected<ImageDecoderInit>();
init->setType("image/png");
init->setData(ArrayBufferOrArrayBufferViewOrReadableStream::FromArrayBuffer(
DOMArrayBuffer::Create(SharedBuffer::Create())));
auto* decoder = ImageDecoderExternal::Create(v8_scope.GetScriptState(), init,
v8_scope.GetExceptionState());
EXPECT_FALSE(decoder);
EXPECT_TRUE(v8_scope.GetExceptionState().HadException());
}
TEST_F(ImageDecoderTest, DecodeNeuteredAtConstruction) {
V8TestingScope v8_scope;
auto* init = MakeGarbageCollected<ImageDecoderInit>();
auto* buffer = DOMArrayBuffer::Create(SharedBuffer::Create());
init->setType("image/png");
init->setData(
ArrayBufferOrArrayBufferViewOrReadableStream::FromArrayBuffer(buffer));
ArrayBufferContents contents;
ASSERT_TRUE(buffer->Transfer(v8_scope.GetIsolate(), contents));
auto* decoder = ImageDecoderExternal::Create(v8_scope.GetScriptState(), init,
v8_scope.GetExceptionState());
EXPECT_FALSE(decoder);
EXPECT_TRUE(v8_scope.GetExceptionState().HadException());
}
TEST_F(ImageDecoderTest, DecodeNeuteredAtDecodeTime) {
V8TestingScope v8_scope;
constexpr char kImageType[] = "image/gif";
EXPECT_TRUE(ImageDecoderExternal::canDecodeType(kImageType));
auto* init = MakeGarbageCollected<ImageDecoderInit>();
init->setType(kImageType);
constexpr char kTestFile[] = "images/resources/animated.gif";
auto data = ReadFile(kTestFile);
DCHECK(!data->IsEmpty()) << "Missing file: " << kTestFile;
auto* buffer = DOMArrayBuffer::Create(std::move(data));
init->setData(
ArrayBufferOrArrayBufferViewOrReadableStream::FromArrayBuffer(buffer));
auto* decoder = ImageDecoderExternal::Create(v8_scope.GetScriptState(), init,
v8_scope.GetExceptionState());
ASSERT_TRUE(decoder);
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
ArrayBufferContents contents;
ASSERT_TRUE(buffer->Transfer(v8_scope.GetIsolate(), contents));
auto promise = decoder->decode(MakeOptions(0, true));
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsRejected());
}
TEST_F(ImageDecoderTest, DecodeUnsupported) {
V8TestingScope v8_scope;
constexpr char kImageType[] = "image/svg+xml";
EXPECT_FALSE(ImageDecoderExternal::canDecodeType(kImageType));
auto* decoder =
CreateDecoder(&v8_scope, "images/resources/test.svg", kImageType);
EXPECT_FALSE(decoder);
EXPECT_TRUE(v8_scope.GetExceptionState().HadException());
}
TEST_F(ImageDecoderTest, DecoderCreationMixedCaseMimeType) {
V8TestingScope v8_scope;
constexpr char kImageType[] = "image/GiF";
EXPECT_TRUE(ImageDecoderExternal::canDecodeType(kImageType));
auto* decoder =
CreateDecoder(&v8_scope, "images/resources/animated.gif", kImageType);
ASSERT_TRUE(decoder);
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
EXPECT_EQ(decoder->type(), "image/gif");
}
TEST_F(ImageDecoderTest, DecodeGif) {
V8TestingScope v8_scope;
constexpr char kImageType[] = "image/gif";
EXPECT_TRUE(ImageDecoderExternal::canDecodeType(kImageType));
auto* decoder =
CreateDecoder(&v8_scope, "images/resources/animated.gif", kImageType);
ASSERT_TRUE(decoder);
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
{
auto promise = decoder->decodeMetadata();
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
}
EXPECT_EQ(decoder->type(), kImageType);
EXPECT_EQ(decoder->frameCount(), 2u);
EXPECT_EQ(decoder->repetitionCount(), 0u);
EXPECT_EQ(decoder->complete(), true);
auto tracks = decoder->tracks();
EXPECT_EQ(tracks.size(), 1u);
EXPECT_EQ(tracks[0]->id(), 0u);
EXPECT_EQ(tracks[0]->animated(), true);
{
auto promise = decoder->decode(MakeOptions(0, true));
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
auto* frame = ToImageFrame(&v8_scope, tester.Value());
EXPECT_TRUE(frame->complete());
EXPECT_EQ(frame->duration(), 0u);
EXPECT_EQ(frame->orientation(), 1u);
auto* bitmap = frame->image();
EXPECT_EQ(bitmap->Size(), IntSize(16, 16));
}
{
auto promise = decoder->decode(MakeOptions(1, true));
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
auto* frame = ToImageFrame(&v8_scope, tester.Value());
EXPECT_TRUE(frame->complete());
EXPECT_EQ(frame->duration(), 0u);
EXPECT_EQ(frame->orientation(), 1u);
auto* bitmap = frame->image();
EXPECT_EQ(bitmap->Size(), IntSize(16, 16));
}
// Decoding past the end should result in a rejected promise.
auto promise = decoder->decode(MakeOptions(3, true));
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsRejected());
}
// TODO(crbug.com/1073995): Add tests for each format, selectTrack(), partial
// decoding, and ImageBitmapOptions.
} // namespace
} // namespace blink