blob: 66bbf46cc84291e7310389ecf1fdddc583184d3a [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 <iterator>
#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/location.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/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/web_applications/commands/internal/callback_command.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/locks/all_apps_lock.h"
#include "chrome/browser/web_applications/locks/app_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/web_app_test.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_observer_test_utils.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 WebAppCommand<LockType> {
public:
explicit MockCommand(LockType::LockDescription lock_description,
base::OnceClosure on_complete)
: WebAppCommand<LockType>("MockCommand",
std::move(lock_description),
std::move(on_complete)) {}
MOCK_METHOD(void, OnDestruction, ());
~MockCommand() override { OnDestruction(); }
void StartWithLock(std::unique_ptr<LockType> lock) override {
lock_ = std::move(lock);
StartAfterStoringLock();
}
MOCK_METHOD(void, StartAfterStoringLock, ());
base::WeakPtr<MockCommand> AsWeakPtr() { return weak_factory_.GetWeakPtr(); }
void PostCompleteAndSelfDestruct(CommandResult result) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&MockCommand::CallCompleteAndSelfDestruct,
weak_factory_.GetWeakPtr(), result));
}
void CallCompleteAndSelfDestruct(CommandResult result) {
WebAppCommand<LockType>::CompleteAndSelfDestruct(result);
}
private:
std::unique_ptr<LockType> lock_;
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());
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::test::TestFuture<void>& command1_done,
base::WeakPtr<MockCommand<LockType2>> command2_ptr,
base::test::TestFuture<void>& command2_done) {
ASSERT_TRUE(command1_ptr && command2_ptr);
EXPECT_FALSE(command1_ptr->IsStarted() || command2_ptr->IsStarted());
{
testing::InSequence in_sequence;
EXPECT_CALL(*command1_ptr, StartAfterStoringLock())
.Times(1)
.WillOnce([&]() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
// Post this to catch if the second command runs before this
// one completes.
command1_ptr->PostCompleteAndSelfDestruct(
CommandResult::kSuccess);
}));
});
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command2_ptr, StartAfterStoringLock())
.Times(1)
.WillOnce([&]() {
EXPECT_FALSE(command1_ptr);
command2_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command1_done.Wait());
ASSERT_TRUE(command2_done.Wait());
}
EXPECT_FALSE(command1_ptr);
EXPECT_FALSE(command2_ptr);
}
template <typename LockType1, typename LockType2>
void CheckCommandsRunInParallel(
base::WeakPtr<MockCommand<LockType1>> command1_ptr,
base::test::TestFuture<void>& command1_done,
base::WeakPtr<MockCommand<LockType2>> command2_ptr,
base::test::TestFuture<void>& command2_done) {
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, StartAfterStoringLock()).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, StartAfterStoringLock()).WillOnce([&]() {
command2_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
command1_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command2_done.Wait());
ASSERT_TRUE(command1_done.Wait());
}
}
};
TEST_F(WebAppCommandManagerTest, SimpleCommand) {
// Simple test of a command enqueued, starting, and completing.
base::test::TestFuture<void> command_complete;
auto mock_command =
std::make_unique<::testing::StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> 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, StartAfterStoringLock()).WillOnce([&]() {
loop.Quit();
});
loop.Run();
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
command_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
}
EXPECT_TRUE(command_complete.Wait());
EXPECT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, CompleteInStart) {
// Test to make sure the command can complete & destroy itself in the Start
// method.
base::test::TestFuture<void> command_complete;
auto command =
std::make_unique<::testing::StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
{
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, StartAfterStoringLock()).Times(1).WillOnce([&]() {
ASSERT_TRUE(command_ptr);
command_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
EXPECT_TRUE(command_complete.Wait());
}
}
TEST_F(WebAppCommandManagerTest, TwoQueues) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId), command1_complete.GetCallback());
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId2), command2_complete.GetCallback());
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, command1_complete, command2_ptr,
command2_complete);
}
TEST_F(WebAppCommandManagerTest, MixedQueueTypes) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command1_complete.GetCallback());
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId), command2_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> 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, command1_complete, command2_ptr,
command2_complete);
base::test::TestFuture<void> command3_complete;
auto command3 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command3_complete.GetCallback());
base::test::TestFuture<void> command4_complete;
auto command4 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command4_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command3_ptr = command3->AsWeakPtr();
base::WeakPtr<MockCommand<SharedWebContentsLock>> command4_ptr =
command4->AsWeakPtr();
// One about:blank load per web contents lock.
manager().ScheduleCommand(std::move(command3));
manager().ScheduleCommand(std::move(command4));
// All app lock does not block web contents command.
CheckCommandsRunInParallel(command3_ptr, command3_complete, command4_ptr,
command4_complete);
base::test::TestFuture<void> command5_complete;
auto command5 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId), command5_complete.GetCallback());
base::test::TestFuture<void> command6_complete;
auto command6 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command6_complete.GetCallback());
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, command5_complete, command6_ptr,
command6_complete);
}
TEST_F(WebAppCommandManagerTest, SingleAppQueue) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId), command1_complete.GetCallback());
base::WeakPtr<MockCommand<AppLock>> command1_ptr = command1->AsWeakPtr();
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<StrictMock<MockCommand<AppLock>>>(
AppLockDescription(kTestAppId), command2_complete.GetCallback());
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command1_complete, command2_ptr,
command2_complete);
}
TEST_F(WebAppCommandManagerTest, GlobalQueue) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command1_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command1_ptr = command1->AsWeakPtr();
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command2_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command1_complete, command2_ptr,
command2_complete);
}
TEST_F(WebAppCommandManagerTest, BackgroundWebContentsQueue) {
base::test::TestFuture<void> command1_complete;
auto command1 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command1_complete.GetCallback());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command1_ptr =
command1->AsWeakPtr();
base::test::TestFuture<void> command2_complete;
auto command2 =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command2_complete.GetCallback());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command2_ptr =
command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
CheckCommandsRunInOrder(command1_ptr, command1_complete, command2_ptr,
command2_complete);
}
TEST_F(WebAppCommandManagerTest, ShutdownPreStartCommand) {
base::test::TestFuture<void> on_command_complete;
auto command = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), on_command_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command_ptr = command->AsWeakPtr();
manager().ScheduleCommand(std::move(command));
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
manager().Shutdown();
EXPECT_TRUE(on_command_complete.Wait());
}
TEST_F(WebAppCommandManagerTest, ShutdownStartedCommand) {
base::test::TestFuture<void> command_complete;
auto mock_command = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> 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, StartAfterStoringLock()).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
testing::InSequence in_sequence;
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
manager().Shutdown();
ASSERT_TRUE(command_complete.Wait());
EXPECT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, ScheduleAfterShutdown) {
base::test::TestFuture<void> command_complete;
auto mock_command = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command_ptr =
mock_command->AsWeakPtr();
manager().Shutdown();
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
manager().ScheduleCommand(std::move(mock_command));
EXPECT_TRUE(command_complete.Wait());
ASSERT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, ShutdownQueuedCommand) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command1_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command1_ptr = command1->AsWeakPtr();
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<StrictMock<MockCommand<AllAppsLock>>>(
AllAppsLockDescription(), command2_complete.GetCallback());
base::WeakPtr<MockCommand<AllAppsLock>> command2_ptr = command2->AsWeakPtr();
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
{
base::RunLoop loop;
EXPECT_CALL(*command1_ptr, StartAfterStoringLock).WillOnce([&]() {
loop.Quit();
});
loop.Run();
}
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
manager().Shutdown();
ASSERT_TRUE(command1_complete.Wait());
ASSERT_TRUE(command2_complete.Wait());
}
TEST_F(WebAppCommandManagerTest, MultipleSingleArgCallbackCommands) {
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<std::string(AppLock&, base::Value::Dict&)> callback =
base::BindOnce([](webapps::AppId app_id, AppLock&,
base::Value::Dict&) { return app_id; },
app_id);
manager().ScheduleCommand(
std::make_unique<
internal::CallbackCommandWithResult<AppLock, std::string>>(
"", AppLockDescription(app_id), std::move(callback),
/*on_complete=*/barrier, "shutdown"));
}
loop.Run();
}
TEST_F(WebAppCommandManagerTest, AppWithSharedWebContents) {
base::test::TestFuture<void> command1_complete;
auto command1 = std::make_unique<MockCommand<SharedWebContentsWithAppLock>>(
SharedWebContentsWithAppLockDescription({kTestAppId}),
command1_complete.GetCallback());
base::test::TestFuture<void> command2_complete;
auto command2 = std::make_unique<MockCommand<AppLock>>(
AppLockDescription(kTestAppId), command2_complete.GetCallback());
base::test::TestFuture<void> command3_complete;
auto command3 = std::make_unique<MockCommand<SharedWebContentsLock>>(
SharedWebContentsLockDescription(), command3_complete.GetCallback());
base::WeakPtr<MockCommand<SharedWebContentsWithAppLock>> command1_ptr =
command1->AsWeakPtr();
base::WeakPtr<MockCommand<AppLock>> command2_ptr = command2->AsWeakPtr();
base::WeakPtr<MockCommand<SharedWebContentsLock>> command3_ptr =
command3->AsWeakPtr();
// One about:blank load per web contents lock.
manager().ScheduleCommand(std::move(command1));
manager().ScheduleCommand(std::move(command2));
manager().ScheduleCommand(std::move(command3));
{
testing::InSequence in_sequence;
EXPECT_CALL(*command1_ptr, StartAfterStoringLock())
.Times(1)
.WillOnce([&]() {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
command1_ptr->CallCompleteAndSelfDestruct(
CommandResult::kSuccess);
}));
});
EXPECT_CALL(*command2_ptr, StartAfterStoringLock()).Times(0);
EXPECT_CALL(*command3_ptr, StartAfterStoringLock()).Times(0);
EXPECT_CALL(*command1_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command1_complete.Wait());
}
EXPECT_FALSE(command1_ptr);
{
EXPECT_CALL(*command2_ptr, StartAfterStoringLock())
.Times(1)
.WillOnce([&]() {
command2_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command3_ptr, StartAfterStoringLock())
.Times(1)
.WillOnce([&]() {
command3_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command2_ptr, OnDestruction()).Times(1);
EXPECT_CALL(*command3_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command2_complete.Wait());
ASSERT_TRUE(command3_complete.Wait());
}
EXPECT_FALSE(command1_ptr);
EXPECT_FALSE(command2_ptr);
EXPECT_FALSE(command3_ptr);
}
TEST_F(WebAppCommandManagerTest, ToDebugValue) {
base::test::TestFuture<void> on_command_complete;
manager().ScheduleCommand(
std::make_unique<internal::CallbackCommand<AppLock>>(
"", AppLockDescription(kTestAppId),
base::DoNothingAs<void(AppLock&, base::Value::Dict&)>(),
on_command_complete.GetCallback()));
manager().ScheduleCommand(
std::make_unique<internal::CallbackCommand<AppLock>>(
"", AppLockDescription(kTestAppId2),
base::DoNothingAs<void(AppLock&, base::Value::Dict&)>(),
base::DoNothing()));
EXPECT_TRUE(on_command_complete.Wait());
// Generally we should not test the output of the debug value, as this seems
// fragile & can duplicate testing of the real functionality. However for the
// common metadata it seems fine.
base::Value::Dict command_manager_debug_value =
manager().ToDebugValue().TakeDict();
auto get_metadata_field_names =
[](const base::Value::Dict& command_dict) -> std::vector<std::string> {
std::vector<std::string> names;
const base::Value::Dict* metadata = command_dict.FindDict("!metadata");
std::transform(
metadata->cbegin(), metadata->cend(), std::back_inserter(names),
[](base::Value::Dict::const_iterator::reference pair) -> std::string {
return pair.first;
});
return names;
};
base::Value::List* log = command_manager_debug_value.FindList("command_log");
ASSERT_TRUE(log);
ASSERT_GT(log->size(), 0ul);
EXPECT_THAT(
get_metadata_field_names(log->front().GetDict()),
::testing::UnorderedElementsAre(
"command_result", "completion_location", "id", "initial_lock_request",
"name", "result", "started", "scheduled_location"));
base::Value::List* queue =
command_manager_debug_value.FindList("command_queue");
ASSERT_TRUE(queue);
ASSERT_GT(queue->size(), 0ul);
EXPECT_THAT(
get_metadata_field_names(queue->front().GetDict()),
::testing::UnorderedElementsAre("id", "initial_lock_request", "name",
"started", "scheduled_location"));
}
TEST_F(WebAppCommandManagerTest, DestroySharedWebContentsOnPostTask) {
base::test::TestFuture<void> command_done;
auto mock_command =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command_done.GetCallback());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command_ptr =
mock_command->AsWeakPtr();
// Queue & complete a command with shared web contents.
{
testing::InSequence in_sequence;
manager().ScheduleCommand(std::move(mock_command));
ASSERT_TRUE(command_ptr);
EXPECT_FALSE(command_ptr->IsStarted());
EXPECT_CALL(*command_ptr, StartAfterStoringLock()).WillOnce([&]() {
command_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command_done.Wait());
}
// Validate web contents is destroyed.
{
// Verify the shared web contents is not destroyed yet.
content::WebContents* web_contents = manager().web_contents_for_testing();
EXPECT_TRUE(web_contents);
// Wait for web contents to be destroyed.
content::WebContentsDestroyedWatcher content_destroyed_observer(
web_contents);
content_destroyed_observer.Wait();
// Verify the web contents is now destroyed.
EXPECT_FALSE(manager().web_contents_for_testing());
}
EXPECT_FALSE(command_ptr);
}
TEST_F(WebAppCommandManagerTest, DestroySharedWebContentsOnShutdown) {
base::test::TestFuture<void> command_done;
auto mock_command =
std::make_unique<StrictMock<MockCommand<SharedWebContentsLock>>>(
SharedWebContentsLockDescription(), command_done.GetCallback());
base::WeakPtr<MockCommand<SharedWebContentsLock>> command_ptr =
mock_command->AsWeakPtr();
// Queue and complete the command.
{
testing::InSequence in_sequence;
manager().ScheduleCommand(std::move(mock_command));
ASSERT_TRUE(command_ptr);
EXPECT_FALSE(command_ptr->IsStarted());
EXPECT_CALL(*command_ptr, StartAfterStoringLock()).WillOnce([&]() {
command_ptr->CallCompleteAndSelfDestruct(CommandResult::kSuccess);
});
EXPECT_CALL(*command_ptr, OnDestruction()).Times(1);
ASSERT_TRUE(command_done.Wait());
}
// Validate the web contents are not destroyed right away.
{
EXPECT_TRUE(manager().web_contents_for_testing());
// Trigger shutdown and validate web contents is destroyed.
manager().Shutdown();
EXPECT_FALSE(manager().web_contents_for_testing());
}
EXPECT_FALSE(command_ptr);
}
} // namespace
} // namespace web_app