blob: 6745b6d97fdb19d03e861a52ef6c4fc25721fbe3 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2019 The Chromium Authors
Ken Rockot27a62d52019-10-30 01:57:012// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Lukasz Anforowicz84dbd4d42021-09-09 19:58:215#include <limits>
6
7#include "base/base_paths.h"
8#include "base/files/file_path.h"
9#include "base/files/file_util.h"
Avi Drissmanadac21992023-01-11 23:46:3910#include "base/functional/callback_helpers.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2111#include "base/path_service.h"
Sean Maher52fa5a72022-11-14 15:53:2512#include "base/task/sequenced_task_runner.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2113#include "base/test/bind.h"
14#include "base/test/metrics/histogram_tester.h"
15#include "base/threading/thread_restrictions.h"
Gabriel Charetted87f10f2022-03-31 00:44:2216#include "base/time/time.h"
Ken Rockot27a62d52019-10-30 01:57:0117#include "content/public/browser/service_process_host.h"
Peter Kasting919ce652020-05-07 10:22:3618#include "content/public/test/browser_test.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2119#include "content/public/test/browser_test_utils.h"
Ken Rockot27a62d52019-10-30 01:57:0120#include "content/public/test/content_browser_test.h"
21#include "mojo/public/cpp/bindings/remote.h"
22#include "services/data_decoder/public/cpp/data_decoder.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2123#include "services/data_decoder/public/cpp/decode_image.h"
Ken Rockot27a62d52019-10-30 01:57:0124#include "services/data_decoder/public/mojom/data_decoder_service.mojom.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2125#include "services/data_decoder/public/mojom/image_decoder.mojom.h"
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2126#include "testing/gmock/include/gmock/gmock.h"
27
28using ::testing::Pair;
29using ::testing::UnorderedElementsAre;
Ken Rockot27a62d52019-10-30 01:57:0130
31namespace content {
32
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2133namespace {
34
35// Populates `output` and returns true on success (i.e. if `relative_path`
36// exists and can be read into `output`). Otherwise returns false.
37bool ReadTestFile(const base::FilePath& relative_path,
38 std::vector<uint8_t>& output) {
39 base::FilePath source_root_dir;
Ho Cheunga18707a2023-10-18 15:30:4040 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_root_dir)) {
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2141 return false;
Ho Cheunga18707a2023-10-18 15:30:4042 }
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2143
44 std::string file_contents_as_string;
45 {
46 base::ScopedAllowBlockingForTesting allow_file_io_for_testing;
47 base::FilePath absolute_path = source_root_dir.Append(relative_path);
48 if (!base::ReadFileToString(absolute_path, &file_contents_as_string))
49 return false;
50 }
51
52 // Convert chars to uint8_ts.
53 for (const char& c : file_contents_as_string)
54 output.push_back(c);
55
56 return true;
57}
58
59// Populates `out_measurement_value` and returns true on success (i.e. if the
60// `metric_name` has a single measurement in `histograms`). Otherwise returns
61// false.
62bool GetSingleMeasurement(const base::HistogramTester& histograms,
63 const char* metric_name,
64 base::TimeDelta& out_measurement_value) {
65 DCHECK(metric_name);
66
67 std::vector<base::Bucket> buckets = histograms.GetAllSamples(metric_name);
68 if (buckets.size() != 1u)
69 return false;
70
71 EXPECT_EQ(1u, buckets.size());
Peter Kastinge5a38ed2021-10-02 03:06:3572 out_measurement_value = base::Milliseconds(buckets.front().min);
Lukasz Anforowicz84dbd4d42021-09-09 19:58:2173 return true;
74}
75
76} // namespace
77
Ken Rockot27a62d52019-10-30 01:57:0178using DataDecoderBrowserTest = ContentBrowserTest;
79
80class ServiceProcessObserver : public ServiceProcessHost::Observer {
81 public:
82 ServiceProcessObserver() { ServiceProcessHost::AddObserver(this); }
83
Peter Boström828b9022021-09-21 02:28:4384 ServiceProcessObserver(const ServiceProcessObserver&) = delete;
85 ServiceProcessObserver& operator=(const ServiceProcessObserver&) = delete;
86
Ken Rockot27a62d52019-10-30 01:57:0187 ~ServiceProcessObserver() override {
88 ServiceProcessHost::RemoveObserver(this);
89 }
90
91 int instances_started() const { return instances_started_; }
92
93 void WaitForNextLaunch() {
94 launch_wait_loop_.emplace();
95 launch_wait_loop_->Run();
96 }
97
98 void OnServiceProcessLaunched(const ServiceProcessInfo& info) override {
99 if (info.IsService<data_decoder::mojom::DataDecoderService>()) {
100 ++instances_started_;
101 if (launch_wait_loop_)
102 launch_wait_loop_->Quit();
103 }
104 }
105
106 private:
Arthur Sonzognic686e8f2024-01-11 08:36:37107 std::optional<base::RunLoop> launch_wait_loop_;
Ken Rockot27a62d52019-10-30 01:57:01108 int instances_started_ = 0;
Ken Rockot27a62d52019-10-30 01:57:01109};
110
111IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, Launch) {
112 ServiceProcessObserver observer;
113
114 // Verifies that the DataDecoder client object launches a service process as
115 // needed.
116 data_decoder::DataDecoder decoder;
117
118 // |GetService()| must always ensure a connection to the service on all
119 // platforms, so we use it instead of a more specific API whose behavior may
120 // vary across platforms.
121 decoder.GetService();
122
123 observer.WaitForNextLaunch();
124 EXPECT_EQ(1, observer.instances_started());
125}
126
127IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, LaunchIsolated) {
128 ServiceProcessObserver observer;
129
130 // Verifies that separate DataDecoder client objects will launch separate
Daniel Chengbc846a3b2025-05-08 22:31:36131 // service processes. We also bind an ImageDecoder interface to ensure that
132 // the instances don't go idle.
Ken Rockot27a62d52019-10-30 01:57:01133 data_decoder::DataDecoder decoder1;
Daniel Chengbc846a3b2025-05-08 22:31:36134 mojo::Remote<data_decoder::mojom::ImageDecoder> image_decoder1;
135 decoder1.GetService()->BindImageDecoder(
136 image_decoder1.BindNewPipeAndPassReceiver());
Ken Rockot27a62d52019-10-30 01:57:01137 observer.WaitForNextLaunch();
138 EXPECT_EQ(1, observer.instances_started());
139
140 data_decoder::DataDecoder decoder2;
Daniel Chengbc846a3b2025-05-08 22:31:36141 mojo::Remote<data_decoder::mojom::ImageDecoder> image_decoder2;
142 decoder2.GetService()->BindImageDecoder(
143 image_decoder2.BindNewPipeAndPassReceiver());
Ken Rockot27a62d52019-10-30 01:57:01144 observer.WaitForNextLaunch();
145 EXPECT_EQ(2, observer.instances_started());
146
147 // Both interfaces should be connected end-to-end.
Daniel Chengbc846a3b2025-05-08 22:31:36148 image_decoder1.FlushForTesting();
149 image_decoder2.FlushForTesting();
150 EXPECT_TRUE(image_decoder1.is_connected());
151 EXPECT_TRUE(image_decoder2.is_connected());
Ken Rockot27a62d52019-10-30 01:57:01152}
153
Lukasz Anforowicz84dbd4d42021-09-09 19:58:21154IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, DecodeImageIsolated) {
155 std::vector<uint8_t> file_contents;
156 base::FilePath content_test_data_path = GetTestDataFilePath();
157 base::FilePath png_path =
158 content_test_data_path.AppendASCII("site_isolation/png-corp.png");
159 ASSERT_TRUE(ReadTestFile(png_path, file_contents));
160
161 base::HistogramTester histograms;
162 {
163 base::RunLoop run_loop;
164 data_decoder::DecodeImageCallback callback =
165 base::BindLambdaForTesting([&](const SkBitmap& decoded_bitmap) {
166 EXPECT_EQ(100, decoded_bitmap.width());
167 EXPECT_EQ(100, decoded_bitmap.height());
168 run_loop.Quit();
169 });
170 data_decoder::DecodeImageIsolated(
171 file_contents, data_decoder::mojom::ImageCodec::kDefault,
172 false, // shrink_to_fit
173 std::numeric_limits<uint32_t>::max(), // max_size_in_bytes
174 gfx::Size(), // desired_image_frame_size
175 std::move(callback));
176 run_loop.Run();
177 }
178
179 FetchHistogramsFromChildProcesses();
180 EXPECT_THAT(
181 histograms.GetTotalCountsForPrefix("Security.DataDecoder"),
182 UnorderedElementsAre(
183 Pair("Security.DataDecoder.Image.Isolated.EndToEndTime", 1),
184 Pair("Security.DataDecoder.Image.Isolated.ProcessOverhead", 1),
185 Pair("Security.DataDecoder.Image.DecodingTime", 1)));
186
187 base::TimeDelta end_to_end_duration_estimate;
188 EXPECT_TRUE(GetSingleMeasurement(
189 histograms, "Security.DataDecoder.Image.Isolated.EndToEndTime",
190 end_to_end_duration_estimate));
191
192 base::TimeDelta overhead_estimate;
193 EXPECT_TRUE(GetSingleMeasurement(
194 histograms, "Security.DataDecoder.Image.Isolated.ProcessOverhead",
195 overhead_estimate));
196
197 base::TimeDelta decoding_duration_estimate;
198 EXPECT_TRUE(GetSingleMeasurement(histograms,
199 "Security.DataDecoder.Image.DecodingTime",
200 decoding_duration_estimate));
201
202 EXPECT_LE(decoding_duration_estimate, end_to_end_duration_estimate);
203 EXPECT_LE(overhead_estimate, end_to_end_duration_estimate);
204}
205
206IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, DecodeImage) {
207 std::vector<uint8_t> file_contents;
208 base::FilePath content_test_data_path = GetTestDataFilePath();
209 base::FilePath png_path =
210 content_test_data_path.AppendASCII("site_isolation/png-corp.png");
211 ASSERT_TRUE(ReadTestFile(png_path, file_contents));
212
213 base::HistogramTester histograms;
214 {
215 base::RunLoop run_loop;
216 data_decoder::DecodeImageCallback callback =
217 base::BindLambdaForTesting([&](const SkBitmap& decoded_bitmap) {
218 EXPECT_EQ(100, decoded_bitmap.width());
219 EXPECT_EQ(100, decoded_bitmap.height());
220 run_loop.Quit();
221 });
222
223 data_decoder::DataDecoder decoder;
224 data_decoder::DecodeImage(
225 &decoder, file_contents, data_decoder::mojom::ImageCodec::kDefault,
226 false, // shrink_to_fit
227 std::numeric_limits<uint32_t>::max(), // max_size_in_bytes
228 gfx::Size(), // desired_image_frame_size
229 std::move(callback));
230 run_loop.Run();
231 }
232
233 FetchHistogramsFromChildProcesses();
234 EXPECT_THAT(
235 histograms.GetTotalCountsForPrefix("Security.DataDecoder"),
236 UnorderedElementsAre(
237 Pair("Security.DataDecoder.Image.Reusable.EndToEndTime", 1),
238 Pair("Security.DataDecoder.Image.Reusable.ProcessOverhead", 1),
239 Pair("Security.DataDecoder.Image.DecodingTime", 1)));
240
241 base::TimeDelta end_to_end_duration_estimate;
242 EXPECT_TRUE(GetSingleMeasurement(
243 histograms, "Security.DataDecoder.Image.Reusable.EndToEndTime",
244 end_to_end_duration_estimate));
245
246 base::TimeDelta overhead_estimate;
247 EXPECT_TRUE(GetSingleMeasurement(
248 histograms, "Security.DataDecoder.Image.Reusable.ProcessOverhead",
249 overhead_estimate));
250
251 base::TimeDelta decoding_duration_estimate;
252 EXPECT_TRUE(GetSingleMeasurement(histograms,
253 "Security.DataDecoder.Image.DecodingTime",
254 decoding_duration_estimate));
255
256 EXPECT_LE(decoding_duration_estimate, end_to_end_duration_estimate);
257 EXPECT_LE(overhead_estimate, end_to_end_duration_estimate);
258}
259
Robert Sesek31db6732022-05-26 20:43:26260IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest,
261 NoCallbackAfterDestruction_Json) {
262 base::RunLoop run_loop;
263
264 auto decoder = std::make_unique<data_decoder::DataDecoder>();
265 auto* raw_decoder = decoder.get();
266
267 // Android's in-process parser can complete synchronously, so queue the
268 // delete task first unlike in the other tests.
Sean Maher52fa5a72022-11-14 15:53:25269 base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
270 FROM_HERE, std::move(decoder));
Robert Sesek31db6732022-05-26 20:43:26271
272 bool got_callback = false;
273 raw_decoder->ParseJson(
274 "[1, 2, 3]",
275 base::BindOnce(
276 [](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
277 data_decoder::DataDecoder::ValueOrError result) {
278 *got_callback = true;
279 },
280 // Pass the quit closure as a ScopedClosureRunner, so that the loop
281 // is quit if the callback is destroyed un-run or after it runs.
282 &got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
283
284 run_loop.Run();
285
286 EXPECT_FALSE(got_callback);
287}
288
289IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, NoCallbackAfterDestruction_Xml) {
290 base::RunLoop run_loop;
291
292 auto decoder = std::make_unique<data_decoder::DataDecoder>();
293 bool got_callback = false;
294 decoder->ParseXml(
295 "<marquee>hello world</marquee>",
296 data_decoder::mojom::XmlParser::WhitespaceBehavior::kIgnore,
297 base::BindOnce(
298 [](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
299 data_decoder::DataDecoder::ValueOrError result) {
300 *got_callback = true;
301 },
302 // Pass the quit closure as a ScopedClosureRunner, so that the loop
303 // is quit if the callback is destroyed un-run or after it runs.
304 &got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
305
Sean Maher52fa5a72022-11-14 15:53:25306 base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
307 FROM_HERE, std::move(decoder));
Robert Sesek31db6732022-05-26 20:43:26308 run_loop.Run();
309
310 EXPECT_FALSE(got_callback);
311}
312
313IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest,
314 NoCallbackAfterDestruction_Gzip) {
315 base::RunLoop run_loop;
316
317 auto decoder = std::make_unique<data_decoder::DataDecoder>();
318 bool got_callback = false;
319 decoder->GzipCompress(
320 {{0x1, 0x1, 0x1, 0x1, 0x1, 0x1}},
321 base::BindOnce(
322 [](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
Claudio DeSouzad2afab42022-07-14 20:40:51323 base::expected<mojo_base::BigBuffer, std::string> result) {
324 *got_callback = true;
325 },
Robert Sesek31db6732022-05-26 20:43:26326 // Pass the quit closure as a ScopedClosureRunner, so that the loop
327 // is quit if the callback is destroyed un-run or after it runs.
328 &got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
329
Sean Maher52fa5a72022-11-14 15:53:25330 base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
331 FROM_HERE, std::move(decoder));
Robert Sesek31db6732022-05-26 20:43:26332 run_loop.Run();
333
334 EXPECT_FALSE(got_callback);
335}
336
Ken Rockot27a62d52019-10-30 01:57:01337} // namespace content