blob: 8f3ef8f3d132f0561960a150e5bc50449b224618 [file]
// 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 "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_SRC_TEST_DATA_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;
}
} // 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:
std::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 an ImageDecoder interface to ensure that
// the instances don't go idle.
data_decoder::DataDecoder decoder1;
mojo::Remote<data_decoder::mojom::ImageDecoder> image_decoder1;
decoder1.GetService()->BindImageDecoder(
image_decoder1.BindNewPipeAndPassReceiver());
observer.WaitForNextLaunch();
EXPECT_EQ(1, observer.instances_started());
data_decoder::DataDecoder decoder2;
mojo::Remote<data_decoder::mojom::ImageDecoder> image_decoder2;
decoder2.GetService()->BindImageDecoder(
image_decoder2.BindNewPipeAndPassReceiver());
observer.WaitForNextLaunch();
EXPECT_EQ(2, observer.instances_started());
// Both interfaces should be connected end-to-end.
image_decoder1.FlushForTesting();
image_decoder2.FlushForTesting();
EXPECT_TRUE(image_decoder1.is_connected());
EXPECT_TRUE(image_decoder2.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();
}
}
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();
}
}
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