Commit 630f1ca7 authored by danakj's avatar danakj Committed by Commit Bot

Require skia.mojom.InlineBitmap pixels to be tightly packed N32 format

This avoids buffer-size mistakes when reading the pixel data. We will
CHECK() on the sender side if the InlineBitmap creator violates this
requirement when converting from an SkBitmap, and we will reject the
message on the receiving side if the number of bytes does not match
exactly w*h*4.

Adds tests for the InlineBitmap expectations, and remove the unit test
and verification code in the extensions host side, as the wire format
can no longer be invalid and require conversion.

Bug: 1144462
Change-Id: Ie90b91513f40a58294ffd4d84dac88624b6082b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2546967Reviewed-by: default avatarFlorin Malita <fmalita@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Commit-Queue: danakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#829342}
parent 0eb86d1c
......@@ -297,49 +297,6 @@ TEST(DeclarativeContentActionTest, SetIcon) {
}
}
TEST(DeclarativeContentActionTest, SetInvalidIcon) {
TestExtensionEnvironment env;
content::RenderViewHostTestEnabler rvh_enabler;
// This bitmap type is allowed by skia::mojom::InlineBitmap but we should
// convert to N32 upon receipt.
SkBitmap bitmap;
EXPECT_TRUE(bitmap.tryAllocPixels(
SkImageInfo::Make(19, 19, kAlpha_8_SkColorType, kPremul_SkAlphaType)));
bitmap.eraseARGB(255, 255, 0, 0);
DictionaryBuilder builder;
builder.Set("instanceType", "declarativeContent.SetIcon");
std::vector<uint8_t> s = skia::mojom::InlineBitmap::Serialize(&bitmap);
builder.Set("imageData", DictionaryBuilder().Set("19", s).Build());
std::unique_ptr<base::DictionaryValue> dict = builder.Build();
const Extension* extension = env.MakeExtension(
ParseJson(R"({"page_action": {"default_title": "Extension"}})"));
base::HistogramTester histogram_tester;
TestingProfile profile;
std::string error;
std::unique_ptr<const ContentAction> result =
ContentAction::Create(&profile, extension, *dict, &error);
EXPECT_EQ("", error);
ASSERT_TRUE(result.get());
ExtensionAction* action = ExtensionActionManager::Get(env.profile())
->GetExtensionAction(*extension);
std::unique_ptr<content::WebContents> contents = env.MakeTab();
const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
ContentAction::ApplyInfo apply_info = {extension, env.profile(),
contents.get(), 100};
EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
result->Apply(apply_info);
EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
EXPECT_EQ(kN32_SkColorType,
action->GetDeclarativeIcon(tab_id).ToSkBitmap()->colorType());
}
TEST(DeclarativeContentActionTest, SetInvisibleIcon) {
TestExtensionEnvironment env;
......
......@@ -17,7 +17,6 @@
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "skia/ext/skia_utils_base.h"
#include "skia/public/mojom/bitmap.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
......@@ -137,20 +136,12 @@ ExtensionAction::IconParseResult ExtensionAction::ParseIconFromCanvasDictionary(
} else {
continue;
}
SkBitmap unsafe_bitmap;
if (!skia::mojom::InlineBitmap::Deserialize(bytes, num_bytes,
&unsafe_bitmap)) {
return IconParseResult::kUnpickleFailure;
}
CHECK(!unsafe_bitmap.isNull());
// On receipt of an arbitrary bitmap from the renderer, we convert to an N32
// 32bpp bitmap. Other pixel sizes can lead to out-of-bounds mistakes when
// transferring the pixels out of the/ bitmap into other buffers.
SkBitmap bitmap;
if (!skia::SkBitmapToN32OpaqueOrPremul(unsafe_bitmap, &bitmap)) {
NOTREACHED() << "Unable to convert bitmap for icon";
if (!skia::mojom::InlineBitmap::Deserialize(bytes, num_bytes, &bitmap)) {
return IconParseResult::kUnpickleFailure;
}
// A well-behaved renderer will never send a null bitmap to us here.
CHECK(!bitmap.isNull());
// Chrome helpfully scales the provided icon(s), but let's not go overboard.
const int kActionIconMaxSize = 10 * ActionIconSize();
......
......@@ -33,10 +33,15 @@ struct BitmapMappedFromTrustedProcess {
mojo_base.mojom.BigBuffer pixel_data;
};
// NOTE: This should only be used when an SkBitmap MUST be serialized as raw
// bytes (i.e. it's not OK for shared memory to be used, as above).
// Encode an N32 SkBitmap for transport without relying on shared memory.
// Normally, it is preferable to use shared memory and this mojom type should
// NOT be used for IPC.
//
// This type is useful, however, for de/serialization to a string (via
// skia::mojom::InlineBitmap::Serialize() and Deserialize()) since it will not
// attempt to use a shared memory handle and will encode the actual pixel
// content always.
struct InlineBitmap {
ImageInfo image_info;
uint64 row_bytes;
BitmapN32ImageInfo image_info;
array<uint8> pixel_data;
};
......@@ -31,104 +31,63 @@ class BigBufferPixelRef final : public SkPixelRef {
mojo_base::BigBuffer buffer_;
};
} // namespace
// static
bool StructTraits<skia::mojom::BitmapDataView, SkBitmap>::IsNull(
const SkBitmap& b) {
return b.isNull();
}
// static
void StructTraits<skia::mojom::BitmapDataView, SkBitmap>::SetToNull(
SkBitmap* b) {
b->reset();
}
// static
const SkImageInfo& StructTraits<skia::mojom::BitmapDataView,
SkBitmap>::image_info(const SkBitmap& b) {
return b.info();
}
// static
uint64_t StructTraits<skia::mojom::BitmapDataView, SkBitmap>::row_bytes(
const SkBitmap& b) {
return b.rowBytes();
}
// static
mojo_base::BigBufferView StructTraits<skia::mojom::BitmapDataView,
SkBitmap>::pixel_data(const SkBitmap& b) {
return mojo_base::BigBufferView(base::make_span(
static_cast<uint8_t*>(b.getPixels()), b.computeByteSize()));
}
// static
bool StructTraits<skia::mojom::BitmapDataView, SkBitmap>::Read(
skia::mojom::BitmapDataView data,
SkBitmap* b) {
SkImageInfo image_info;
if (!data.ReadImageInfo(&image_info))
return false;
bool CreateSkBitmapForPixelData(SkBitmap* b,
int row_bytes,
const SkImageInfo& image_info,
base::span<const uint8_t> pixel_data) {
// Ensure width and height are reasonable.
if (image_info.width() > kMaxWidth || image_info.height() > kMaxHeight)
return false;
*b = SkBitmap();
if (!b->tryAllocPixels(image_info, data.row_bytes())) {
// TODO(danakj): We should always use minRowBytes for all IPC messages.
if (!row_bytes)
row_bytes = image_info.minRowBytes();
if (!b->tryAllocPixels(image_info, row_bytes))
return false;
}
// If the image is empty, return success after setting the image info.
if (image_info.width() == 0 || image_info.height() == 0)
return true;
mojo_base::BigBufferView pixel_data_view;
if (!data.ReadPixelData(&pixel_data_view))
return false;
base::span<const uint8_t> pixel_data_bytes = pixel_data_view.data();
if (b->width() != image_info.width() || b->height() != image_info.height() ||
static_cast<uint64_t>(b->rowBytes()) != data.row_bytes() ||
b->computeByteSize() != pixel_data_bytes.size() || !b->readyToDraw()) {
// If these don't match then the number of bytes sent does not match what the
// rest of the mojom said there should be.
if (pixel_data.size() != b->computeByteSize())
return false;
}
// Implementation note: This copy is important from a security perspective as
// it provides the recipient of the SkBitmap with a stable copy of the data.
// The sender could otherwise continue modifying the shared memory buffer
// underlying the BigBuffer instance.
std::copy(pixel_data_bytes.begin(), pixel_data_bytes.end(),
std::copy(pixel_data.begin(), pixel_data.end(),
static_cast<uint8_t*>(b->getPixels()));
b->notifyPixelsChanged();
return true;
}
// static
bool StructTraits<skia::mojom::BitmapMappedFromTrustedProcessDataView,
SkBitmap>::IsNull(const SkBitmap& b) {
return b.isNull();
}
} // namespace
// static
void StructTraits<skia::mojom::BitmapMappedFromTrustedProcessDataView,
SkBitmap>::SetToNull(SkBitmap* b) {
b->reset();
mojo_base::BigBufferView StructTraits<skia::mojom::BitmapDataView,
SkBitmap>::pixel_data(const SkBitmap& b) {
return mojo_base::BigBufferView(base::make_span(
static_cast<uint8_t*>(b.getPixels()), b.computeByteSize()));
}
// static
const SkImageInfo&
StructTraits<skia::mojom::BitmapMappedFromTrustedProcessDataView,
SkBitmap>::image_info(const SkBitmap& b) {
return b.info();
}
bool StructTraits<skia::mojom::BitmapDataView, SkBitmap>::Read(
skia::mojom::BitmapDataView data,
SkBitmap* b) {
SkImageInfo image_info;
if (!data.ReadImageInfo(&image_info))
return false;
// static
uint64_t StructTraits<skia::mojom::BitmapMappedFromTrustedProcessDataView,
SkBitmap>::row_bytes(const SkBitmap& b) {
return b.rowBytes();
mojo_base::BigBufferView pixel_data_view;
if (!data.ReadPixelData(&pixel_data_view))
return false;
return CreateSkBitmapForPixelData(b, data.row_bytes(), std::move(image_info),
pixel_data_view.data());
}
// static
......@@ -152,8 +111,6 @@ bool StructTraits<
if (image_info.width() > kMaxWidth || image_info.height() > kMaxHeight)
return false;
*b = SkBitmap();
// If the image is empty, return success after setting the image info.
if (image_info.width() == 0 || image_info.height() == 0)
return b->tryAllocPixels(image_info, data.row_bytes());
......@@ -168,6 +125,11 @@ bool StructTraits<
if (!b->setInfo(image_info, data.row_bytes()))
return false;
// If these don't match then the number of bytes sent does not match what the
// rest of the mojom said there should be.
if (b->computeByteSize() != pixel_data_view.data().size())
return false;
// Allow the resultant SkBitmap to refer to the given BigBuffer. Note, the
// sender could continue modifying the pixels of the buffer, which could be a
// security concern for some applications. The trade-off is performance.
......@@ -179,34 +141,11 @@ bool StructTraits<
return true;
}
// static
bool StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap>::IsNull(
const SkBitmap& b) {
return b.isNull();
}
// static
void StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap>::SetToNull(
SkBitmap* b) {
b->reset();
}
// static
const SkImageInfo& StructTraits<skia::mojom::InlineBitmapDataView,
SkBitmap>::image_info(const SkBitmap& b) {
return StructTraits<skia::mojom::BitmapDataView, SkBitmap>::image_info(b);
}
// static
uint64_t StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap>::row_bytes(
const SkBitmap& b) {
return StructTraits<skia::mojom::BitmapDataView, SkBitmap>::row_bytes(b);
}
// static
base::span<const uint8_t>
StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap>::pixel_data(
const SkBitmap& b) {
CHECK_EQ(b.rowBytes(), b.info().minRowBytes());
return base::make_span(static_cast<uint8_t*>(b.getPixels()),
b.computeByteSize());
}
......@@ -219,35 +158,14 @@ bool StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap>::Read(
if (!data.ReadImageInfo(&image_info))
return false;
// Ensure width and height are reasonable.
if (image_info.width() > kMaxWidth || image_info.height() > kMaxHeight)
return false;
*b = SkBitmap();
if (!b->tryAllocPixels(image_info, data.row_bytes()))
return false;
mojo::ArrayDataView<uint8_t> pixel_data_view;
data.GetPixelDataDataView(&pixel_data_view);
// If the image is empty, return success after setting the image info.
if (image_info.width() == 0 || image_info.height() == 0)
return true;
base::span<const uint8_t> pixel_data_bytes(pixel_data_view.data(),
pixel_data_view.size());
mojo::ArrayDataView<uint8_t> data_view;
data.GetPixelDataDataView(&data_view);
if (b->width() != image_info.width() || b->height() != image_info.height() ||
static_cast<uint64_t>(b->rowBytes()) != data.row_bytes() ||
b->computeByteSize() != data_view.size() || !b->readyToDraw()) {
return false;
}
auto bitmap_buffer = base::make_span(static_cast<uint8_t*>(b->getPixels()),
b->computeByteSize());
if (!data.ReadPixelData(&bitmap_buffer) ||
bitmap_buffer.size() != b->computeByteSize()) {
return false;
}
b->notifyPixelsChanged();
return true;
return CreateSkBitmapForPixelData(b, /*row_bytes=*/0, std::move(image_info),
std::move(pixel_data_bytes));
}
} // namespace mojo
......@@ -20,11 +20,13 @@ namespace mojo {
template <>
struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
StructTraits<skia::mojom::BitmapDataView, SkBitmap> {
static bool IsNull(const SkBitmap& b);
static void SetToNull(SkBitmap* b);
static const SkImageInfo& image_info(const SkBitmap& b);
static uint64_t row_bytes(const SkBitmap& b);
static bool IsNull(const SkBitmap& b) { return b.isNull(); }
static void SetToNull(SkBitmap* b) { b->reset(); }
static const SkImageInfo& image_info(const SkBitmap& b) { return b.info(); }
static uint64_t row_bytes(const SkBitmap& b) { return b.rowBytes(); }
static mojo_base::BigBufferView pixel_data(const SkBitmap& b);
static bool Read(skia::mojom::BitmapDataView data, SkBitmap* b);
};
......@@ -32,11 +34,13 @@ template <>
struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
StructTraits<skia::mojom::BitmapMappedFromTrustedProcessDataView,
SkBitmap> {
static bool IsNull(const SkBitmap& b);
static void SetToNull(SkBitmap* b);
static const SkImageInfo& image_info(const SkBitmap& b);
static uint64_t row_bytes(const SkBitmap& b);
static bool IsNull(const SkBitmap& b) { return b.isNull(); }
static void SetToNull(SkBitmap* b) { b->reset(); }
static const SkImageInfo& image_info(const SkBitmap& b) { return b.info(); }
static uint64_t row_bytes(const SkBitmap& b) { return b.rowBytes(); }
static mojo_base::BigBufferView pixel_data(const SkBitmap& b);
static bool Read(skia::mojom::BitmapMappedFromTrustedProcessDataView data,
SkBitmap* b);
};
......@@ -44,11 +48,12 @@ struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
template <>
struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
StructTraits<skia::mojom::InlineBitmapDataView, SkBitmap> {
static bool IsNull(const SkBitmap& b);
static void SetToNull(SkBitmap* b);
static const SkImageInfo& image_info(const SkBitmap& b);
static uint64_t row_bytes(const SkBitmap& b);
static bool IsNull(const SkBitmap& b) { return b.isNull(); }
static void SetToNull(SkBitmap* b) { b->reset(); }
static const SkImageInfo& image_info(const SkBitmap& b) { return b.info(); }
static base::span<const uint8_t> pixel_data(const SkBitmap& b);
static bool Read(skia::mojom::InlineBitmapDataView data, SkBitmap* b);
};
......
......@@ -54,3 +54,25 @@ struct ImageInfo {
// no explicit color space.
array<float, 9>? color_to_xyz_matrix;
};
// Similar to ImageInfo, but is used when only N32 ColorType is allowed. As such
// the ColorType is not transmitted over the wire at all.
struct BitmapN32ImageInfo {
AlphaType alpha_type;
uint32 width;
uint32 height;
// Color transfer function mapping encoded values to linear values,
// represented by this 7-parameter piecewise function:
// linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d
// = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
// (A simple gamma transfer function sets g to gamma and a to 1.)
// See SkColorSpace and skcms_TransferFunction. Null if the image has no
// explicit color space. Parameters are serialized as: g, a, b, c, d, e, f.
array<float, 7>? color_transfer_function;
// Color transformation matrix to convert colors to XYZ D50, represented as
// a row-major 3x3 matrix. See SkColorSpace::MakeRGB(). Null if the image has
// no explicit color space.
array<float, 9>? color_to_xyz_matrix;
};
......@@ -11,6 +11,39 @@
namespace mojo {
namespace {
SkImageInfo MakeSkImageInfo(SkColorType color_type,
SkAlphaType alpha_type,
int width,
int height,
mojo::ArrayDataView<float> color_transfer_function,
mojo::ArrayDataView<float> color_to_xyz_matrix) {
sk_sp<SkColorSpace> color_space;
if (!color_transfer_function.is_null() && !color_to_xyz_matrix.is_null()) {
const float* data = color_transfer_function.data();
skcms_TransferFunction transfer_function;
CHECK_EQ(7u, color_transfer_function.size());
transfer_function.g = data[0];
transfer_function.a = data[1];
transfer_function.b = data[2];
transfer_function.c = data[3];
transfer_function.d = data[4];
transfer_function.e = data[5];
transfer_function.f = data[6];
skcms_Matrix3x3 to_xyz_matrix;
CHECK_EQ(9u, color_to_xyz_matrix.size());
memcpy(to_xyz_matrix.vals, color_to_xyz_matrix.data(), 9 * sizeof(float));
color_space = SkColorSpace::MakeRGB(transfer_function, to_xyz_matrix);
}
return SkImageInfo::Make(width, height, color_type, alpha_type,
std::move(color_space));
}
} // namespace
// static
skia::mojom::AlphaType EnumTraits<skia::mojom::AlphaType, SkAlphaType>::ToMojom(
SkAlphaType type) {
......@@ -116,18 +149,6 @@ bool EnumTraits<skia::mojom::ColorType, SkColorType>::FromMojom(
return false;
}
// static
SkColorType StructTraits<skia::mojom::ImageInfoDataView,
SkImageInfo>::color_type(const SkImageInfo& info) {
return info.colorType();
}
// static
SkAlphaType StructTraits<skia::mojom::ImageInfoDataView,
SkImageInfo>::alpha_type(const SkImageInfo& info) {
return info.alphaType();
}
// static
uint32_t StructTraits<skia::mojom::ImageInfoDataView, SkImageInfo>::width(
const SkImageInfo& info) {
......@@ -186,33 +207,28 @@ bool StructTraits<skia::mojom::ImageInfoDataView, SkImageInfo>::Read(
mojo::ArrayDataView<float> color_to_xyz_matrix;
data.GetColorToXyzMatrixDataView(&color_to_xyz_matrix);
// Sender must supply both color space fields or neither. This approach is
// simpler than having an optional ColorSpace mojo struct, due to BUILD.gn
// complexity with blink variants.
if (color_transfer_function.is_null() != color_to_xyz_matrix.is_null())
return false;
*info = MakeSkImageInfo(color_type, alpha_type, data.width(), data.height(),
std::move(color_transfer_function),
std::move(color_to_xyz_matrix));
return true;
}
sk_sp<SkColorSpace> sk_color_space;
if (!color_transfer_function.is_null() && !color_to_xyz_matrix.is_null()) {
const float* data = color_transfer_function.data();
skcms_TransferFunction transfer_function;
CHECK_EQ(7u, color_transfer_function.size());
transfer_function.g = data[0];
transfer_function.a = data[1];
transfer_function.b = data[2];
transfer_function.c = data[3];
transfer_function.d = data[4];
transfer_function.e = data[5];
transfer_function.f = data[6];
// static
bool StructTraits<skia::mojom::BitmapN32ImageInfoDataView, SkImageInfo>::Read(
skia::mojom::BitmapN32ImageInfoDataView data,
SkImageInfo* info) {
SkAlphaType alpha_type;
if (!data.ReadAlphaType(&alpha_type))
return false;
skcms_Matrix3x3 to_xyz_matrix;
CHECK_EQ(9u, color_to_xyz_matrix.size());
memcpy(to_xyz_matrix.vals, color_to_xyz_matrix.data(), 9 * sizeof(float));
sk_color_space = SkColorSpace::MakeRGB(transfer_function, to_xyz_matrix);
}
mojo::ArrayDataView<float> color_transfer_function;
data.GetColorTransferFunctionDataView(&color_transfer_function);
mojo::ArrayDataView<float> color_to_xyz_matrix;
data.GetColorToXyzMatrixDataView(&color_to_xyz_matrix);
*info = SkImageInfo::Make(data.width(), data.height(), color_type, alpha_type,
std::move(sk_color_space));
*info = MakeSkImageInfo(kN32_SkColorType, alpha_type, data.width(),
data.height(), std::move(color_transfer_function),
std::move(color_to_xyz_matrix));
return true;
}
......
......@@ -31,8 +31,12 @@ struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
template <>
struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
StructTraits<skia::mojom::ImageInfoDataView, SkImageInfo> {
static SkColorType color_type(const SkImageInfo& info);
static SkAlphaType alpha_type(const SkImageInfo& info);
static SkColorType color_type(const SkImageInfo& info) {
return info.colorType();
}
static SkAlphaType alpha_type(const SkImageInfo& info) {
return info.alphaType();
}
static uint32_t width(const SkImageInfo& info);
static uint32_t height(const SkImageInfo& info);
static base::Optional<std::vector<float>> color_transfer_function(
......@@ -43,6 +47,37 @@ struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
static bool Read(skia::mojom::ImageInfoDataView data, SkImageInfo* info);
};
template <>
struct COMPONENT_EXPORT(SKIA_SHARED_TRAITS)
StructTraits<skia::mojom::BitmapN32ImageInfoDataView, SkImageInfo> {
static SkAlphaType alpha_type(const SkImageInfo& info) {
// BitmapN32ImageInfo only allows N32 SkImageInfos.
CHECK_EQ(info.colorType(), kN32_SkColorType);
return info.alphaType();
}
static uint32_t width(const SkImageInfo& info) {
return ImageInfoStructTraits::width(info);
}
static uint32_t height(const SkImageInfo& info) {
return ImageInfoStructTraits::height(info);
}
static base::Optional<std::vector<float>> color_transfer_function(
const SkImageInfo& info) {
return ImageInfoStructTraits::color_transfer_function(info);
}
static base::Optional<std::vector<float>> color_to_xyz_matrix(
const SkImageInfo& info) {
return ImageInfoStructTraits::color_to_xyz_matrix(info);
}
static bool Read(skia::mojom::BitmapN32ImageInfoDataView data,
SkImageInfo* info);
private:
using ImageInfoStructTraits =
StructTraits<skia::mojom::ImageInfoDataView, SkImageInfo>;
};
} // namespace mojo
#endif // SKIA_PUBLIC_MOJOM_IMAGE_INFO_MOJOM_TRAITS_H_
......@@ -4,6 +4,7 @@
#include "mojo/public/cpp/test_support/test_utils.h"
#include "skia/public/mojom/bitmap.mojom.h"
#include "skia/public/mojom/bitmap_skbitmap_mojom_traits.h"
#include "skia/public/mojom/blur_image_filter_tile_mode.mojom.h"
#include "skia/public/mojom/blur_image_filter_tile_mode_mojom_traits.h"
#include "skia/public/mojom/image_info.mojom.h"
......@@ -19,7 +20,6 @@
#include "ui/gfx/skia_util.h"
namespace skia {
namespace {
// mojo::test::SerializeAndDeserialize() doesn't work for a raw enum, so roll
......@@ -33,7 +33,20 @@ bool SerializeAndDeserialize(SkBlurImageFilter::TileMode* input,
SkBlurImageFilter::TileMode>::FromMojom(mode, output);
}
} // namespace
template <typename MojomType, typename UserType>
bool SerializeAndDeserializeFromMojom(mojo::StructPtr<MojomType>* input,
UserType* output) {
mojo::Message message = MojomType::SerializeAsMessage(input);
// This accurately simulates full serialization to ensure that all attached
// handles are serialized as well. Necessary for DeserializeFromMessage to
// work properly.
mojo::ScopedMessageHandle handle = message.TakeMojoMessage();
message = mojo::Message::CreateFromMessageHandle(&handle);
DCHECK(!message.IsNull());
return MojomType::DeserializeFromMessage(std::move(message), output);
}
TEST(StructTraitsTest, ImageInfo) {
SkImageInfo input = SkImageInfo::Make(
......@@ -85,6 +98,24 @@ TEST(StructTraitsTest, Bitmap) {
EXPECT_TRUE(gfx::BitmapsAreEqual(input, output));
}
TEST(StructTraitsTest, BitmapNull) {
SkBitmap input;
input.setInfo(SkImageInfo::MakeN32Premul(
10, 5,
SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
SkNamedGamut::kRec2020)));
EXPECT_TRUE(input.isNull());
// Null input produces a default-initialized SkBitmap.
SkBitmap output;
ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::Bitmap>(
&input, &output));
EXPECT_EQ(output.info().alphaType(), kUnknown_SkAlphaType);
EXPECT_EQ(output.info().colorType(), kUnknown_SkColorType);
EXPECT_EQ(output.rowBytes(), 0u);
EXPECT_TRUE(output.isNull());
}
TEST(StructTraitsTest, BitmapTooWideToSerialize) {
SkBitmap input;
constexpr int kTooWide = 64 * 1024 + 1;
......@@ -139,4 +170,128 @@ TEST(StructTraitsTest, BlurImageFilterTileMode) {
EXPECT_EQ(input, output);
}
TEST(StructTraitsTest, InlineBitmap) {
SkBitmap input;
input.allocPixels(SkImageInfo::MakeN32Premul(
10, 5,
SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
SkNamedGamut::kRec2020)));
input.eraseColor(SK_ColorYELLOW);
input.erase(SK_ColorTRANSPARENT, SkIRect::MakeXYWH(0, 1, 2, 3));
SkBitmap output;
ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::InlineBitmap>(
&input, &output));
EXPECT_EQ(input.info(), output.info());
EXPECT_EQ(input.rowBytes(), output.rowBytes());
EXPECT_TRUE(gfx::BitmapsAreEqual(input, output));
}
TEST(StructTraitsTest, InlineBitmapNull) {
SkBitmap input;
input.setInfo(SkImageInfo::MakeN32Premul(
10, 5,
SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
SkNamedGamut::kRec2020)));
EXPECT_TRUE(input.isNull());
// Null input produces a default-initialized SkBitmap.
SkBitmap output;
ASSERT_TRUE(mojo::test::SerializeAndDeserialize<skia::mojom::InlineBitmap>(
&input, &output));
EXPECT_EQ(output.info().alphaType(), kUnknown_SkAlphaType);
EXPECT_EQ(output.info().colorType(), kUnknown_SkColorType);
EXPECT_EQ(output.rowBytes(), 0u);
EXPECT_TRUE(output.isNull());
}
TEST(StructTraitsTest, InlineBitmapSerializeToString) {
SkBitmap input;
input.allocPixels(SkImageInfo::MakeN32Premul(10, 5));
input.eraseColor(SK_ColorYELLOW);
// Serialize to string works.
auto serialized = skia::mojom::InlineBitmap::Serialize(&input);
SkBitmap output;
ASSERT_TRUE(
skia::mojom::InlineBitmap::Deserialize(std::move(serialized), &output));
EXPECT_EQ(input.info(), output.info());
EXPECT_EQ(input.rowBytes(), output.rowBytes());
EXPECT_TRUE(gfx::BitmapsAreEqual(input, output));
}
TEST(StructTraitsTest, InlineBitmapSerializeInvalidRowBytes) {
SkBitmap input;
input.allocPixels(SkImageInfo::MakeN32Premul(10, 5), 11 * 4);
// We do not allow sending rowBytes() other than the minRowBytes().
EXPECT_DEATH(skia::mojom::InlineBitmap::SerializeAsMessage(&input), "");
}
TEST(StructTraitsTest, InlineBitmapSerializeInvalidColorType) {
SkBitmap input;
input.allocPixels(SkImageInfo::MakeA8(10, 5));
// We do not allow sending colorType() other than the kN32_SkColorType.
EXPECT_DEATH(skia::mojom::InlineBitmap::SerializeAsMessage(&input), "");
}
// A helper to construct a skia.mojom.InlineBitmap without using StructTraits
// to bypass checks on the sending side.
static mojo::StructPtr<skia::mojom::InlineBitmap> ConstructInlineBitmap(
SkImageInfo info,
std::vector<unsigned char> pixels,
std::vector<float> color_transfer_function = {},
std::vector<float> color_to_xyz_matrix = {}) {
DCHECK_EQ(info.colorType(), kN32_SkColorType);
auto mojom_info = skia::mojom::BitmapN32ImageInfo::New();
mojom_info->alpha_type = info.alphaType();
mojom_info->width = info.width();
mojom_info->height = info.height();
if (!color_transfer_function.empty()) {
DCHECK_EQ(7u, color_transfer_function.size());
mojom_info->color_transfer_function = std::move(color_transfer_function);
}
if (!color_to_xyz_matrix.empty()) {
DCHECK_EQ(9u, color_to_xyz_matrix.size());
mojom_info->color_to_xyz_matrix = std::move(color_to_xyz_matrix);
}
auto inline_bitmap = skia::mojom::InlineBitmap::New();
inline_bitmap->image_info = std::move(mojom_info);
inline_bitmap->pixel_data = std::move(pixels);
return inline_bitmap;
}
TEST(StructTraitsTest, VerifyInlineBitmapConstruction) {
// Verify that we can manually construct a valid InlineBitmap and deserialize
// it successfully.
mojo::StructPtr<skia::mojom::InlineBitmap> input =
ConstructInlineBitmap(SkImageInfo::MakeN32Premul(1, 1), {1, 2, 3, 4});
SkBitmap output;
bool ok = SerializeAndDeserializeFromMojom<skia::mojom::InlineBitmap>(
&input, &output);
EXPECT_TRUE(ok);
}
TEST(StructTraitsTest, InlineBitmapDeserializeTooFewBytes) {
mojo::StructPtr<skia::mojom::InlineBitmap> input =
ConstructInlineBitmap(SkImageInfo::MakeN32Premul(2, 1), {1, 2, 3, 4});
SkBitmap output;
bool ok = SerializeAndDeserializeFromMojom<skia::mojom::InlineBitmap>(
&input, &output);
EXPECT_FALSE(ok);
}
TEST(StructTraitsTest, InlineBitmapDeserializeTooManyBytes) {
mojo::StructPtr<skia::mojom::InlineBitmap> input = ConstructInlineBitmap(
SkImageInfo::MakeN32Premul(1, 1), {1, 2, 3, 4, 5, 6, 7, 8});
SkBitmap output;
bool ok = SerializeAndDeserializeFromMojom<skia::mojom::InlineBitmap>(
&input, &output);
EXPECT_FALSE(ok);
}
} // namespace
} // namespace skia
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment