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