blob: 31b1e70f57e0278931201bc0ca7cd6c08e561292 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/javascript_dialogs/app_modal_dialog_queue.h"
#include <memory>
#include "base/functional/callback_helpers.h"
#include "components/javascript_dialogs/app_modal_dialog_controller.h"
#include "components/javascript_dialogs/app_modal_dialog_view.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace javascript_dialogs {
namespace {
// A view, a la what is returned by AppModalDialogManager::AppModalViewFactory,
// to own an AppModalDialogController.
class FakeView : public AppModalDialogView {
public:
explicit FakeView(std::unique_ptr<AppModalDialogController> controller)
: controller_(std::move(controller)) {}
void ShowAppModalDialog() override {}
void ActivateAppModalDialog() override {}
void CloseAppModalDialog() override {}
void AcceptAppModalDialog() override {}
void CancelAppModalDialog() override {}
bool IsShowing() const override { return true; }
private:
std::unique_ptr<AppModalDialogController> controller_;
};
class TestDialogControllerBase : public AppModalDialogController {
public:
explicit TestDialogControllerBase(bool* was_destroyed)
: AppModalDialogController(nullptr,
&extra_data_,
u"test",
content::JAVASCRIPT_DIALOG_TYPE_ALERT,
u"msg",
u"",
false,
false,
false,
base::DoNothing()),
was_destroyed_(was_destroyed) {}
~TestDialogControllerBase() override {
if (was_destroyed_) {
*was_destroyed_ = true;
}
}
private:
ExtraDataMap extra_data_;
raw_ptr<bool> was_destroyed_ = nullptr;
};
// A dialog controller whose ShowModalDialog() sets `view_` to the fake owning
// view, allowing CompleteDialog() to trigger ShowNextDialog() on destruction.
class TestDialogController : public TestDialogControllerBase {
public:
explicit TestDialogController(bool* was_shown,
bool* was_destroyed,
std::unique_ptr<FakeView>* owning_view)
: TestDialogControllerBase(was_destroyed),
was_shown_(was_shown),
owning_view_(owning_view) {}
void ShowModalDialog(
std::unique_ptr<AppModalDialogController> controller) override {
if (was_shown_) {
*was_shown_ = true;
}
*owning_view_ = std::make_unique<FakeView>(std::move(controller));
view_ = owning_view_->get();
}
void TriggerComplete() { CompleteDialog(); }
private:
raw_ptr<bool> was_shown_ = nullptr;
raw_ptr<std::unique_ptr<FakeView>> owning_view_ = nullptr;
};
// A dialog controller that fails the test if shown. Used to verify that
// CancelAllDialogs() prevents queued dialogs from being displayed.
class FailIfShownDialogController : public TestDialogControllerBase {
public:
using TestDialogControllerBase::TestDialogControllerBase;
void ShowModalDialog(
std::unique_ptr<AppModalDialogController> controller) override {
ADD_FAILURE() << "Dialog must not be shown after CancelAllDialogs()";
}
};
class AppModalDialogQueueTest : public testing::Test {
protected:
void TearDown() override {
AppModalDialogQueue::GetInstance()->ResetForTesting();
}
};
// Verifies that CancelAllDialogs() prevents queued dialogs from being shown
// when the active dialog completes during shutdown.
TEST_F(AppModalDialogQueueTest, CancelAllDialogsPreventsShowDuringShutdown) {
auto* queue = AppModalDialogQueue::GetInstance();
bool dialog1_shown = false;
std::unique_ptr<FakeView> dialog1_view;
bool dialog2_destroyed = false;
auto dialog1 = std::make_unique<TestDialogController>(
&dialog1_shown, /*was_destroyed=*/nullptr, &dialog1_view);
auto* dialog1_ptr = dialog1.get();
queue->AddDialog(std::move(dialog1));
ASSERT_TRUE(dialog1_shown);
ASSERT_TRUE(queue->HasActiveDialog());
auto dialog2 =
std::make_unique<FailIfShownDialogController>(&dialog2_destroyed);
queue->AddDialog(std::move(dialog2));
ASSERT_FALSE(dialog2_destroyed);
// Without this call, dialog1->TriggerComplete() would show dialog2.
queue->CancelAllDialogs();
EXPECT_TRUE(dialog2_destroyed);
dialog1_ptr->TriggerComplete();
EXPECT_FALSE(queue->HasActiveDialog());
dialog1_ptr->Invalidate();
}
TEST_F(AppModalDialogQueueTest, AddDialogDeletesAfterShutdown) {
auto* queue = AppModalDialogQueue::GetInstance();
queue->CancelAllDialogs();
bool destroyed = false;
auto dialog = std::make_unique<FailIfShownDialogController>(&destroyed);
queue->AddDialog(std::move(dialog));
EXPECT_TRUE(destroyed);
EXPECT_FALSE(queue->HasActiveDialog());
}
TEST_F(AppModalDialogQueueTest, ShowNextDialogDrainsQueueAfterShutdown) {
auto* queue = AppModalDialogQueue::GetInstance();
bool dialog1_shown = false;
std::unique_ptr<FakeView> dialog1_view;
auto dialog1 = std::make_unique<TestDialogController>(
&dialog1_shown, /*was_destroyed=*/nullptr, &dialog1_view);
auto* dialog1_ptr = dialog1.get();
queue->AddDialog(std::move(dialog1));
ASSERT_TRUE(dialog1_shown);
bool destroyed2 = false;
bool destroyed3 = false;
queue->AddDialog(std::make_unique<FailIfShownDialogController>(&destroyed2));
queue->AddDialog(std::make_unique<FailIfShownDialogController>(&destroyed3));
queue->CancelAllDialogs();
EXPECT_TRUE(destroyed2);
EXPECT_TRUE(destroyed3);
dialog1_ptr->TriggerComplete();
EXPECT_FALSE(queue->HasActiveDialog());
dialog1_ptr->Invalidate();
}
} // namespace
} // namespace javascript_dialogs