1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <memory>
6 #include <vector>
7
8 #include "base/bind.h"
9 #include "base/lazy_instance.h"
10 #include "base/stl_util.h"
11 #include "base/test/task_environment.h"
12 #include "gin/array_buffer.h"
13 #include "gin/public/isolate_holder.h"
14 #include "mojo/public/cpp/bindings/binder_map.h"
15 #include "services/data_decoder/image_decoder_impl.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/blink/public/web/blink.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "ui/gfx/codec/jpeg_codec.h"
20
21 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
22 #include "gin/v8_initializer.h"
23 #endif
24
25 namespace data_decoder {
26
27 namespace {
28
29 const int64_t kTestMaxImageSize = 128 * 1024;
30
31 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
32 #if defined(USE_V8_CONTEXT_SNAPSHOT)
33 constexpr gin::V8Initializer::V8SnapshotFileType kSnapshotType =
34 gin::V8Initializer::V8SnapshotFileType::kWithAdditionalContext;
35 #else
36 constexpr gin::V8Initializer::V8SnapshotFileType kSnapshotType =
37 gin::V8Initializer::V8SnapshotFileType::kDefault;
38 #endif
39 #endif
40
CreateJPEGImage(int width,int height,SkColor color,std::vector<unsigned char> * output)41 bool CreateJPEGImage(int width,
42 int height,
43 SkColor color,
44 std::vector<unsigned char>* output) {
45 SkBitmap bitmap;
46 bitmap.allocN32Pixels(width, height);
47 bitmap.eraseColor(color);
48
49 constexpr int kQuality = 50;
50 if (!gfx::JPEGCodec::Encode(bitmap, kQuality, output)) {
51 LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
52 return false;
53 }
54 return true;
55 }
56
57 class Request {
58 public:
Request(ImageDecoderImpl * decoder)59 explicit Request(ImageDecoderImpl* decoder) : decoder_(decoder) {}
60
DecodeImage(const std::vector<unsigned char> & image,bool shrink)61 void DecodeImage(const std::vector<unsigned char>& image, bool shrink) {
62 decoder_->DecodeImage(
63 image, mojom::ImageCodec::DEFAULT, shrink, kTestMaxImageSize,
64 gfx::Size(), // Take the smallest frame (there's only one frame).
65 base::BindOnce(&Request::OnRequestDone, base::Unretained(this)));
66 }
67
bitmap() const68 const SkBitmap& bitmap() const { return bitmap_; }
69
70 private:
OnRequestDone(const SkBitmap & result_image)71 void OnRequestDone(const SkBitmap& result_image) { bitmap_ = result_image; }
72
73 ImageDecoderImpl* decoder_;
74 SkBitmap bitmap_;
75 };
76
77 // We need to ensure that Blink and V8 are initialized in order to use content's
78 // image decoding call.
79 class BlinkInitializer : public blink::Platform {
80 public:
BlinkInitializer()81 BlinkInitializer() {
82 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
83 gin::V8Initializer::LoadV8Snapshot(kSnapshotType);
84 #endif // V8_USE_EXTERNAL_STARTUP_DATA
85
86 mojo::BinderMap binders;
87 blink::CreateMainThreadAndInitialize(this, &binders);
88 }
89
~BlinkInitializer()90 ~BlinkInitializer() override {}
91
92 private:
93 DISALLOW_COPY_AND_ASSIGN(BlinkInitializer);
94 };
95
96 base::LazyInstance<BlinkInitializer>::Leaky g_blink_initializer =
97 LAZY_INSTANCE_INITIALIZER;
98
99 class ImageDecoderImplTest : public testing::Test {
100 public:
101 ImageDecoderImplTest() = default;
102 ~ImageDecoderImplTest() override = default;
103
SetUp()104 void SetUp() override { g_blink_initializer.Get(); }
105
106 protected:
decoder()107 ImageDecoderImpl* decoder() { return &decoder_; }
108
109 private:
110 base::test::SingleThreadTaskEnvironment task_environment_;
111 ImageDecoderImpl decoder_;
112 };
113
114 } // namespace
115
116 // Test that DecodeImage() doesn't return image message > (max message size)
TEST_F(ImageDecoderImplTest,DecodeImageSizeLimit)117 TEST_F(ImageDecoderImplTest, DecodeImageSizeLimit) {
118 // Approx max height for 3:2 image that will fit in the allotted space.
119 // 1.5 for width/height ratio, 4 for bytes/pixel.
120 int max_height_for_msg = sqrt(kTestMaxImageSize / (1.5 * 4));
121 int base_msg_size = sizeof(skia::mojom::Bitmap::Data_);
122
123 // Sizes which should trigger dimension-halving 0, 1 and 2 times
124 int heights[] = {max_height_for_msg - 10, max_height_for_msg + 10,
125 2 * max_height_for_msg + 10};
126 int widths[] = {heights[0] * 3 / 2, heights[1] * 3 / 2, heights[2] * 3 / 2};
127 for (size_t i = 0; i < base::size(heights); i++) {
128 std::vector<unsigned char> jpg;
129 ASSERT_TRUE(CreateJPEGImage(widths[i], heights[i], SK_ColorRED, &jpg));
130
131 Request request(decoder());
132 request.DecodeImage(jpg, true);
133 ASSERT_FALSE(request.bitmap().isNull());
134
135 // Check that image has been shrunk appropriately
136 EXPECT_LT(request.bitmap().computeByteSize() + base_msg_size,
137 static_cast<uint64_t>(kTestMaxImageSize));
138 // Android does its own image shrinking for memory conservation deeper in
139 // the decode, so more specific tests here won't work.
140 #if !defined(OS_ANDROID)
141 EXPECT_EQ(widths[i] >> i, request.bitmap().width());
142 EXPECT_EQ(heights[i] >> i, request.bitmap().height());
143
144 // Check that if resize not requested and image exceeds IPC size limit,
145 // an empty image is returned
146 if (heights[i] > max_height_for_msg) {
147 Request request(decoder());
148 request.DecodeImage(jpg, false);
149 EXPECT_TRUE(request.bitmap().isNull());
150 }
151 #endif
152 }
153 }
154
TEST_F(ImageDecoderImplTest,DecodeImageFailed)155 TEST_F(ImageDecoderImplTest, DecodeImageFailed) {
156 // The "jpeg" is just some "random" data;
157 const char kRandomData[] = "u gycfy7xdjkhfgui bdui ";
158 std::vector<unsigned char> jpg(kRandomData,
159 kRandomData + sizeof(kRandomData));
160
161 Request request(decoder());
162 request.DecodeImage(jpg, false);
163 EXPECT_TRUE(request.bitmap().isNull());
164 }
165
166 } // namespace data_decoder
167