blob: 3670d064e4105d4e6d464f15e37af88a9b2b9efd [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include <memory>
#include <vector>
#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/values.h"
#include "chrome/browser/web_applications/commands/callback_command.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/full_system_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_url_loader.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
class WebContents;
}
namespace web_app {
namespace {
using ::testing::StrictMock;
template <typename LockType>
class MockCommand : public WebAppCommandTemplate<LockType> {
public:
explicit MockCommand(
std::unique_ptr<typename LockType::LockDescription> lock_description)
: WebAppCommandTemplate<LockType>("MockCommand"),
lock_description_(std::move(lock_description)) {}
MOCK_METHOD(void, OnDestruction, ());
~MockCommand() override { OnDestruction(); }
const LockDescription& lock_description() const override {
return *lock_description_;
}
MOCK_METHOD(void, StartWithLock, (std::unique_ptr<LockType>), (override));
MOCK_METHOD(void, OnSyncSourceRemoved, (), (override));
MOCK_METHOD(void, OnShutdown, (), (override));
base::WeakPtr<MockCommand> AsWeakPtr() { return weak_factory_.GetWeakPtr(); }
base::Value ToDebugValue() const override {
return base::Value("FakeCommand");
}
void CallSignalCompletionAndSelfDestruct(
CommandResult result,
base::OnceClosure completion_callback) {
WebAppCommand::SignalCompletionAndSelfDestruct(
result, std::move(completion_callback));
}
private:
std::unique_ptr<LockDescription> lock_description_;
base::WeakPtrFactory<MockCommand> weak_factory_{this};
};
class WebAppCommandManagerTest : public WebAppTest {
public:
static const constexpr char kTestAppId[] = "test_app_id_1";
static const constexpr char kTestAppId2[] = "test_app_id_2";
WebAppCommandManagerTest() = default;
~WebAppCommandManagerTest() override = default;
WebAppCommandManager& manager() {
return WebAppProvider::GetForTest(profile())->command_manager();
}
void SetUp() override {
WebAppTest::SetUp();
FakeWebAppProvider* provider = FakeWebAppProvider::Get(profile());
auto command_url_loader = std::make_unique<TestWebAppUrlLoader>();
url_loader_ = command_url_loader.get();
provider->GetCommandManager().SetUrlLoaderForTesting(
std::move(command_url_loader));
provider->StartWithSubsystems();
}
void TearDown() override {
// TestingProfile checks for leaked RenderWidgetHosts before shutting down
// the profile, so we must shutdown first to delete the shared web contents
// before tearing down.
manager().Shutdown();
WebAppTest::TearDown();
}
template <typename LockType1, typename LockType2>
void CheckCommandsRunInOrder(
base::WeakPtr<MockCommand<LockType1>> command1_ptr,
base::WeakPtr<MockCommand<LockType2>> command2_ptr) {
ASSERT_TRUE(command1_ptr && command2_ptr);
EXPECT_FALSE(command1_ptr->IsStarted() || command2_ptr->IsStarted());
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
{
base::RunLoop loop;
testing::InSequence in_sequence;
EXPECT_CALL(*command1_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
command1_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
}));
});
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1);
EXPECT_CALL(*command2_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
EXPECT_FALSE(command1_ptr);
command2_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
});
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
EXPECT_FALSE(command1_ptr);
EXPECT_FALSE(command2_ptr);
}
template <typename LockType1, typename LockType2>
void CheckCommandsRunInParallel(
base::WeakPtr<MockCommand<LockType1>> command1_ptr,
base::WeakPtr<MockCommand<LockType2>> command2_ptr) {
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
ASSERT_TRUE(command1_ptr && command2_ptr);
EXPECT_FALSE(command1_ptr->IsStarted() || command2_ptr->IsStarted());
{
base::RunLoop loop;
testing::InSequence in_sequence;
EXPECT_CALL(*command1_ptr, StartWithLock(testing::_)).Times(1);
// Only signal completion of command1 after command2 is started to test
// that starting of command2 is not blocked by command1.
EXPECT_CALL(*command2_ptr, StartWithLock(testing::_)).WillOnce([&]() {
command2_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
command1_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
});
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1);
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
}
TestWebAppUrlLoader* url_loader() const { return url_loader_.get(); }
private:
base::raw_ptr<TestWebAppUrlLoader> url_loader_;
};
TEST_F(WebAppCommandManagerTest, SimpleCommand) {
// Simple test of a command enqueued, starting, and completing.
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
auto mock_command =
std::make_unique<::testing::StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command_ptr =
mock_command->AsWeakPtr();
manager().ScheduleCommand(std::move(mock_command));
ASSERT_TRUE(command_ptr);
EXPECT_FALSE(command_ptr->IsStarted());
{
base::RunLoop loop;
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
loop.Quit();
});
loop.Run();
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1);
command_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
mock_closure.Get());
}
EXPECT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, CompleteInStart) {
// Test to make sure the command can complete & destroy itself in the Start
// method.
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
auto command =
std::make_unique<::testing::StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
{
base::RunLoop loop;
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
ASSERT_TRUE(command_ptr);
command_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1).WillOnce([&]() { loop.Quit(); });
loop.Run();
}
}
TEST_F(WebAppCommandManagerTest, TwoQueues) {
auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId2));
base::WeakPtr<MockCommand<AppLock>> command1_ptr = command1->AsWeakPtr();
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInParallel(command1_ptr, command2_ptr);
}
TEST_F(WebAppCommandManagerTest, MixedQueueTypes) {
auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
command1->AsWeakPtr();
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
// Global command blocks app command.
CheckCommandsRunInOrder(command1_ptr, command2_ptr);
auto command3 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
auto command4 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
std::make_unique<SharedWebContentsLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command3_ptr =
command3->AsWeakPtr();
base::WeakPtr<MockCommand<SharedWebContentsLock>> command4_ptr =
command4->AsWeakPtr();
// One about:blank load per web contents lock.
url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
manager().ScheduleCommand(std::move(command3));
manager().ScheduleCommand(std::move(command4));
// Global command blocks web contents command.
CheckCommandsRunInOrder(command3_ptr, command4_ptr);
url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
auto command5 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
auto command6 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
std::make_unique<SharedWebContentsLockDescription>());
base::WeakPtr<MockCommand<AppLock>> command5_ptr = command5->AsWeakPtr();
base::WeakPtr<MockCommand<SharedWebContentsLock>> command6_ptr =
command6->AsWeakPtr();
manager().ScheduleCommand(std::move(command5));
manager().ScheduleCommand(std::move(command6));
// App command and web contents command queue are independent.
CheckCommandsRunInParallel(command5_ptr, command6_ptr);
}
TEST_F(WebAppCommandManagerTest, SingleAppQueue) {
auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
base::WeakPtr<MockCommand<AppLock>> command1_ptr = command1->AsWeakPtr();
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command2_ptr);
}
TEST_F(WebAppCommandManagerTest, GlobalQueue) {
auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
command1->AsWeakPtr();
auto command2 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command2_ptr =
command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command2_ptr);
}
TEST_F(WebAppCommandManagerTest, BackgroundWebContentsQueue) {
auto command1 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
std::make_unique<SharedWebContentsLockDescription>());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command1_ptr =
command1->AsWeakPtr();
auto command2 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
std::make_unique<SharedWebContentsLockDescription>());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command2_ptr =
command2->AsWeakPtr();
url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded,
WebAppUrlLoader::Result::kUrlLoaded});
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command2_ptr);
}
TEST_F(WebAppCommandManagerTest, ShutdownPreStartCommand) {
auto command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
manager().Shutdown();
}
TEST_F(WebAppCommandManagerTest, ShutdownStartedCommand) {
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
auto mock_command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command_ptr =
mock_command->AsWeakPtr();
manager().ScheduleCommand(std::move(mock_command));
ASSERT_TRUE(command_ptr);
EXPECT_FALSE(command_ptr->IsStarted());
{
base::RunLoop loop;
EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
{
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, OnShutdown()).Times(1);
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
}
manager().Shutdown();
EXPECT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, ShutdownQueuedCommand) {
auto command1 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command1_ptr =
command1->AsWeakPtr();
auto command2 = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command2_ptr =
command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
{
base::RunLoop loop;
EXPECT_CALL(*command1_ptr, StartWithLock(testing::_)).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
EXPECT_CALL(*command1_ptr, OnShutdown()).Times(1);
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
}
TEST_F(WebAppCommandManagerTest, OnShutdownCallsCompleteAndDestruct) {
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
auto command = std::make_unique<StrictMock<MockCommand<FullSystemLock>>>(
std::make_unique<FullSystemLockDescription>());
base::WeakPtr<MockCommand<FullSystemLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
{
base::RunLoop loop;
EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
{
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, OnShutdown()).Times(1).WillOnce([&]() {
ASSERT_TRUE(command_ptr);
command_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
mock_closure.Get());
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1);
}
manager().Shutdown();
}
TEST_F(WebAppCommandManagerTest, NotifySyncCallsCompleteAndDestruct) {
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
auto command = std::make_unique<StrictMock<MockCommand<AppLock>>>(
std::make_unique<AppLockDescription>(kTestAppId));
base::WeakPtr<MockCommand<AppLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
{
base::RunLoop loop;
EXPECT_CALL(*command_ptr, StartWithLock(testing::_)).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
{
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, OnSyncSourceRemoved()).Times(1).WillOnce([&]() {
ASSERT_TRUE(command_ptr);
command_ptr->CallSignalCompletionAndSelfDestruct(CommandResult::kSuccess,
mock_closure.Get());
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run()).Times(1);
}
manager().NotifySyncSourceRemoved({kTestAppId});
}
TEST_F(WebAppCommandManagerTest, MultipleCallbackCommands) {
base::RunLoop loop;
// Queue multiple callbacks to app queues, and gather output.
auto barrier = base::BarrierCallback<std::string>(
2, base::BindLambdaForTesting([&](std::vector<std::string> result) {
EXPECT_EQ(result.size(), 2u);
loop.Quit();
}));
for (auto* app_id : {kTestAppId, kTestAppId2}) {
base::OnceCallback<void(AppLock&)> callback = base::BindOnce(
[](AppId app_id, base::RepeatingCallback<void(std::string)> barrier,
AppLock&) { barrier.Run(app_id); },
app_id, barrier);
manager().ScheduleCommand(std::make_unique<CallbackCommand<AppLock>>(
"", std::make_unique<AppLockDescription>(app_id), std::move(callback)));
}
loop.Run();
}
TEST_F(WebAppCommandManagerTest, AppWithSharedWebContents) {
auto command1 = std::make_unique<MockCommand<SharedWebContentsWithAppLock>>(
std::make_unique<SharedWebContentsWithAppLockDescription,
base::flat_set<AppId>>({kTestAppId}));
auto command2 = std::make_unique<MockCommand<AppLock>>(
std::make_unique<AppLockDescription>(kTestAppId));
auto command3 = std::make_unique<MockCommand<SharedWebContentsLock>>(
std::make_unique<SharedWebContentsLockDescription>());
base::WeakPtr<MockCommand<SharedWebContentsWithAppLock>> command1_ptr =
command1->AsWeakPtr();
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
base::WeakPtr<MockCommand<SharedWebContentsLock>> command3_ptr =
command3->AsWeakPtr();
testing::StrictMock<base::MockCallback<base::OnceClosure>> mock_closure;
// One about:blank load per web contents lock.
url_loader()->AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded,
WebAppUrlLoader::Result::kUrlLoaded});
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
manager().ScheduleCommand(std::move(command3));
{
base::RunLoop loop;
testing::InSequence in_sequence;
EXPECT_CALL(*command1_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
command1_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
}));
});
EXPECT_CALL(*command2_ptr, StartWithLock(testing::_)).Times(0);
EXPECT_CALL(*command3_ptr, StartWithLock(testing::_)).Times(0);
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run())
.WillOnce(base::test::RunClosure(loop.QuitClosure()));
loop.Run();
}
EXPECT_FALSE(command1_ptr);
{
EXPECT_CALL(*command2_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
command2_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
});
EXPECT_CALL(*command3_ptr, StartWithLock(testing::_))
.Times(1)
.WillOnce([&]() {
command3_ptr->CallSignalCompletionAndSelfDestruct(
CommandResult::kSuccess, mock_closure.Get());
});
base::RunLoop loop;
base::RepeatingClosure barrier =
base::BarrierClosure(2, loop.QuitClosure());
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command3_ptr, OnDestruction()).Times(1);
EXPECT_CALL(mock_closure, Run())
.Times(2)
.WillRepeatedly(base::test::RunClosure(barrier));
loop.Run();
}
EXPECT_FALSE(command1_ptr);
EXPECT_FALSE(command2_ptr);
EXPECT_FALSE(command3_ptr);
}
TEST_F(WebAppCommandManagerTest, ToDebugValue) {
base::RunLoop loop;
manager().ScheduleCommand(std::make_unique<CallbackCommand<AppLock>>(
"", std::make_unique<AppLockDescription>(kTestAppId),
base::BindLambdaForTesting([&](AppLock&) { loop.Quit(); })));
manager().ScheduleCommand(std::make_unique<CallbackCommand<AppLock>>(
"", std::make_unique<AppLockDescription>(kTestAppId2),
base::DoNothingAs<void(AppLock&)>()));
loop.Run();
manager().ToDebugValue();
}
} // namespace
} // namespace web_app