blob: 0d924a215455c0a77ba7d34638eb7d1be7fd2ac4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <limits>
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "services/data_decoder/public/mojom/data_decoder_service.mojom.h"
#include "services/data_decoder/public/mojom/image_decoder.mojom.h"
#include "services/data_decoder/public/mojom/json_parser.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
namespace content {
namespace {
// Populates `output` and returns true on success (i.e. if `relative_path`
// exists and can be read into `output`). Otherwise returns false.
bool ReadTestFile(const base::FilePath& relative_path,
std::vector<uint8_t>& output) {
base::FilePath source_root_dir;
if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir))
return false;
std::string file_contents_as_string;
{
base::ScopedAllowBlockingForTesting allow_file_io_for_testing;
base::FilePath absolute_path = source_root_dir.Append(relative_path);
if (!base::ReadFileToString(absolute_path, &file_contents_as_string))
return false;
}
// Convert chars to uint8_ts.
for (const char& c : file_contents_as_string)
output.push_back(c);
return true;
}
// Populates `out_measurement_value` and returns true on success (i.e. if the
// `metric_name` has a single measurement in `histograms`). Otherwise returns
// false.
bool GetSingleMeasurement(const base::HistogramTester& histograms,
const char* metric_name,
base::TimeDelta& out_measurement_value) {
DCHECK(metric_name);
std::vector<base::Bucket> buckets = histograms.GetAllSamples(metric_name);
if (buckets.size() != 1u)
return false;
EXPECT_EQ(1u, buckets.size());
out_measurement_value = base::Milliseconds(buckets.front().min);
return true;
}
} // namespace
using DataDecoderBrowserTest = ContentBrowserTest;
class ServiceProcessObserver : public ServiceProcessHost::Observer {
public:
ServiceProcessObserver() { ServiceProcessHost::AddObserver(this); }
ServiceProcessObserver(const ServiceProcessObserver&) = delete;
ServiceProcessObserver& operator=(const ServiceProcessObserver&) = delete;
~ServiceProcessObserver() override {
ServiceProcessHost::RemoveObserver(this);
}
int instances_started() const { return instances_started_; }
void WaitForNextLaunch() {
launch_wait_loop_.emplace();
launch_wait_loop_->Run();
}
void OnServiceProcessLaunched(const ServiceProcessInfo& info) override {
if (info.IsService<data_decoder::mojom::DataDecoderService>()) {
++instances_started_;
if (launch_wait_loop_)
launch_wait_loop_->Quit();
}
}
private:
absl::optional<base::RunLoop> launch_wait_loop_;
int instances_started_ = 0;
};
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, Launch) {
ServiceProcessObserver observer;
// Verifies that the DataDecoder client object launches a service process as
// needed.
data_decoder::DataDecoder decoder;
// |GetService()| must always ensure a connection to the service on all
// platforms, so we use it instead of a more specific API whose behavior may
// vary across platforms.
decoder.GetService();
observer.WaitForNextLaunch();
EXPECT_EQ(1, observer.instances_started());
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, LaunchIsolated) {
ServiceProcessObserver observer;
// Verifies that separate DataDecoder client objects will launch separate
// service processes. We also bind a JsonParser interface to ensure that the
// instances don't go idle.
data_decoder::DataDecoder decoder1;
mojo::Remote<data_decoder::mojom::JsonParser> parser1;
decoder1.GetService()->BindJsonParser(parser1.BindNewPipeAndPassReceiver());
observer.WaitForNextLaunch();
EXPECT_EQ(1, observer.instances_started());
data_decoder::DataDecoder decoder2;
mojo::Remote<data_decoder::mojom::JsonParser> parser2;
decoder2.GetService()->BindJsonParser(parser2.BindNewPipeAndPassReceiver());
observer.WaitForNextLaunch();
EXPECT_EQ(2, observer.instances_started());
// Both interfaces should be connected end-to-end.
parser1.FlushForTesting();
parser2.FlushForTesting();
EXPECT_TRUE(parser1.is_connected());
EXPECT_TRUE(parser2.is_connected());
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, DecodeImageIsolated) {
std::vector<uint8_t> file_contents;
base::FilePath content_test_data_path = GetTestDataFilePath();
base::FilePath png_path =
content_test_data_path.AppendASCII("site_isolation/png-corp.png");
ASSERT_TRUE(ReadTestFile(png_path, file_contents));
base::HistogramTester histograms;
{
base::RunLoop run_loop;
data_decoder::DecodeImageCallback callback =
base::BindLambdaForTesting([&](const SkBitmap& decoded_bitmap) {
EXPECT_EQ(100, decoded_bitmap.width());
EXPECT_EQ(100, decoded_bitmap.height());
run_loop.Quit();
});
data_decoder::DecodeImageIsolated(
file_contents, data_decoder::mojom::ImageCodec::kDefault,
false, // shrink_to_fit
std::numeric_limits<uint32_t>::max(), // max_size_in_bytes
gfx::Size(), // desired_image_frame_size
std::move(callback));
run_loop.Run();
}
FetchHistogramsFromChildProcesses();
EXPECT_THAT(
histograms.GetTotalCountsForPrefix("Security.DataDecoder"),
UnorderedElementsAre(
Pair("Security.DataDecoder.Image.Isolated.EndToEndTime", 1),
Pair("Security.DataDecoder.Image.Isolated.ProcessOverhead", 1),
Pair("Security.DataDecoder.Image.DecodingTime", 1)));
base::TimeDelta end_to_end_duration_estimate;
EXPECT_TRUE(GetSingleMeasurement(
histograms, "Security.DataDecoder.Image.Isolated.EndToEndTime",
end_to_end_duration_estimate));
base::TimeDelta overhead_estimate;
EXPECT_TRUE(GetSingleMeasurement(
histograms, "Security.DataDecoder.Image.Isolated.ProcessOverhead",
overhead_estimate));
base::TimeDelta decoding_duration_estimate;
EXPECT_TRUE(GetSingleMeasurement(histograms,
"Security.DataDecoder.Image.DecodingTime",
decoding_duration_estimate));
EXPECT_LE(decoding_duration_estimate, end_to_end_duration_estimate);
EXPECT_LE(overhead_estimate, end_to_end_duration_estimate);
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, DecodeImage) {
std::vector<uint8_t> file_contents;
base::FilePath content_test_data_path = GetTestDataFilePath();
base::FilePath png_path =
content_test_data_path.AppendASCII("site_isolation/png-corp.png");
ASSERT_TRUE(ReadTestFile(png_path, file_contents));
base::HistogramTester histograms;
{
base::RunLoop run_loop;
data_decoder::DecodeImageCallback callback =
base::BindLambdaForTesting([&](const SkBitmap& decoded_bitmap) {
EXPECT_EQ(100, decoded_bitmap.width());
EXPECT_EQ(100, decoded_bitmap.height());
run_loop.Quit();
});
data_decoder::DataDecoder decoder;
data_decoder::DecodeImage(
&decoder, file_contents, data_decoder::mojom::ImageCodec::kDefault,
false, // shrink_to_fit
std::numeric_limits<uint32_t>::max(), // max_size_in_bytes
gfx::Size(), // desired_image_frame_size
std::move(callback));
run_loop.Run();
}
FetchHistogramsFromChildProcesses();
EXPECT_THAT(
histograms.GetTotalCountsForPrefix("Security.DataDecoder"),
UnorderedElementsAre(
Pair("Security.DataDecoder.Image.Reusable.EndToEndTime", 1),
Pair("Security.DataDecoder.Image.Reusable.ProcessOverhead", 1),
Pair("Security.DataDecoder.Image.DecodingTime", 1)));
base::TimeDelta end_to_end_duration_estimate;
EXPECT_TRUE(GetSingleMeasurement(
histograms, "Security.DataDecoder.Image.Reusable.EndToEndTime",
end_to_end_duration_estimate));
base::TimeDelta overhead_estimate;
EXPECT_TRUE(GetSingleMeasurement(
histograms, "Security.DataDecoder.Image.Reusable.ProcessOverhead",
overhead_estimate));
base::TimeDelta decoding_duration_estimate;
EXPECT_TRUE(GetSingleMeasurement(histograms,
"Security.DataDecoder.Image.DecodingTime",
decoding_duration_estimate));
EXPECT_LE(decoding_duration_estimate, end_to_end_duration_estimate);
EXPECT_LE(overhead_estimate, end_to_end_duration_estimate);
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest,
NoCallbackAfterDestruction_Json) {
base::RunLoop run_loop;
auto decoder = std::make_unique<data_decoder::DataDecoder>();
auto* raw_decoder = decoder.get();
// Android's in-process parser can complete synchronously, so queue the
// delete task first unlike in the other tests.
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, std::move(decoder));
bool got_callback = false;
raw_decoder->ParseJson(
"[1, 2, 3]",
base::BindOnce(
[](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
data_decoder::DataDecoder::ValueOrError result) {
*got_callback = true;
},
// Pass the quit closure as a ScopedClosureRunner, so that the loop
// is quit if the callback is destroyed un-run or after it runs.
&got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
run_loop.Run();
EXPECT_FALSE(got_callback);
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest, NoCallbackAfterDestruction_Xml) {
base::RunLoop run_loop;
auto decoder = std::make_unique<data_decoder::DataDecoder>();
bool got_callback = false;
decoder->ParseXml(
"<marquee>hello world</marquee>",
data_decoder::mojom::XmlParser::WhitespaceBehavior::kIgnore,
base::BindOnce(
[](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
data_decoder::DataDecoder::ValueOrError result) {
*got_callback = true;
},
// Pass the quit closure as a ScopedClosureRunner, so that the loop
// is quit if the callback is destroyed un-run or after it runs.
&got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, std::move(decoder));
run_loop.Run();
EXPECT_FALSE(got_callback);
}
IN_PROC_BROWSER_TEST_F(DataDecoderBrowserTest,
NoCallbackAfterDestruction_Gzip) {
base::RunLoop run_loop;
auto decoder = std::make_unique<data_decoder::DataDecoder>();
bool got_callback = false;
decoder->GzipCompress(
{{0x1, 0x1, 0x1, 0x1, 0x1, 0x1}},
base::BindOnce(
[](bool* got_callback, base::ScopedClosureRunner quit_closure_runner,
base::expected<mojo_base::BigBuffer, std::string> result) {
*got_callback = true;
},
// Pass the quit closure as a ScopedClosureRunner, so that the loop
// is quit if the callback is destroyed un-run or after it runs.
&got_callback, base::ScopedClosureRunner(run_loop.QuitClosure())));
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, std::move(decoder));
run_loop.Run();
EXPECT_FALSE(got_callback);
}
} // namespace content