// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>
#include <vector>

#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/notifications/notification_common.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/notifications/notification_permission_context.h"
#include "chrome/browser/notifications/notification_test_util.h"
#include "chrome/browser/notifications/platform_notification_service_factory.h"
#include "chrome/browser/notifications/platform_notification_service_impl.h"
#include "chrome/browser/permissions/permission_manager_factory.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_manager.h"
#include "components/permissions/permission_request_manager.h"
#include "components/site_engagement/content/site_engagement_score.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "content/public/common/content_features.h"
#include "content/public/common/persistent_notification_status.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/strings/grit/ui_strings.h"

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#endif

namespace {

// Dimensions of the icon.png resource in the notification test data directory.
constexpr int kIconWidth = 100;
constexpr int kIconHeight = 100;

constexpr int kNotificationVibrationPattern[] = {100, 200, 300};
constexpr double kNotificationTimestamp = 621046800000.;

constexpr const char kTestFileName[] =
    "notifications/platform_notification_service.html";
constexpr const char kTestNotificationOrigin[] = "https://example.com/";
constexpr const char kTestNotificationId[] = "random#notification-id";

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
constexpr const char kTestNotificationTitle[] = "fake_notification_title";
#endif

}  // namespace

class PlatformNotificationServiceBrowserTest : public InProcessBrowserTest {
 public:
  PlatformNotificationServiceBrowserTest();
  ~PlatformNotificationServiceBrowserTest() override = default;

  void SetUp() override {
    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    https_server_->ServeFilesFromSourceDirectory(server_root_);
    ASSERT_TRUE(https_server_->Start());

    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    display_service_tester_ =
        std::make_unique<NotificationDisplayServiceTester>(
            browser()->GetProfile());

    site_engagement::SiteEngagementScore::SetParamValuesForTesting();
    NavigateToTestPage(std::string("/") + kTestFileName);
  }

  void TearDownOnMainThread() override {
    display_service_tester_.reset();
    InProcessBrowserTest::TearDownOnMainThread();
  }

 protected:
  // Returns the Platform Notification Service these unit tests are for.
  PlatformNotificationServiceImpl* service() const {
    return PlatformNotificationServiceFactory::GetForProfile(
        browser()->GetProfile());
  }

  // Returns a vector with the Notification objects that are being displayed
  // by the notification display service. Synchronous.
  std::vector<message_center::Notification> GetDisplayedNotifications(
      bool is_persistent) const {
    NotificationHandler::Type type =
        is_persistent ? NotificationHandler::Type::WEB_PERSISTENT
                      : NotificationHandler::Type::WEB_NON_PERSISTENT;
    return display_service_tester_->GetDisplayedNotificationsForType(type);
  }

  // Waits until at least |expected_count| notifications of the given type are
  // displayed by the notification display service.
  std::vector<message_center::Notification> WaitForDisplayedNotifications(
      bool is_persistent,
      size_t expected_count = 1u) {
    NotificationHandler::Type type =
        is_persistent ? NotificationHandler::Type::WEB_PERSISTENT
                      : NotificationHandler::Type::WEB_NON_PERSISTENT;
    auto notifications =
        display_service_tester_->GetDisplayedNotificationsForType(type);
    if (notifications.size() >= expected_count) {
      return notifications;
    }

    base::RunLoop run_loop;
    display_service_tester_->SetNotificationAddedClosure(base::BindRepeating(
        [](NotificationDisplayServiceTester* tester,
           NotificationHandler::Type type, size_t count, base::RunLoop* loop) {
          if (tester->GetDisplayedNotificationsForType(type).size() >= count) {
            loop->Quit();
          }
        },
        base::Unretained(display_service_tester_.get()), type, expected_count,
        base::Unretained(&run_loop)));
    run_loop.Run();

    // This is done to "clean up" the repeating closure added above. Since
    // `run_loop` is stack allocated, if multiple notifications are showing up
    // in the same test, the callback above will fire, and the `run_loop`
    // instance will dangle as it will be destroyed as soon as this function
    // has returned.
    display_service_tester_->SetNotificationAddedClosure(
        base::RepeatingClosure());
    return display_service_tester_->GetDisplayedNotificationsForType(type);
  }

  // Grants permission to display Web Notifications for origin of the test
  // page that's being used in this browser test.
  void GrantNotificationPermissionForTest() const {
    NotificationPermissionContext::UpdatePermission(
        browser()->GetProfile(), TestPageUrl().DeprecatedGetOriginAsURL(),
        CONTENT_SETTING_ALLOW);
  }

  // Blocks permission to display Web Notifications for origin of the test
  // page that's being used in this browser test.
  void BlockNotificationPermissionForTest() const {
    NotificationPermissionContext::UpdatePermission(
        browser()->GetProfile(), TestPageUrl().DeprecatedGetOriginAsURL(),
        CONTENT_SETTING_BLOCK);
  }

  bool RequestAndAcceptPermission() {
    return "granted" == RequestAndRespondToPermission(
                            permissions::PermissionRequestManager::ACCEPT_ALL);
  }

  double GetEngagementScore(const GURL& origin) const {
    return site_engagement::SiteEngagementService::Get(browser()->GetProfile())
        ->GetScore(origin);
  }

  GURL GetLastCommittedURL() const {
    return browser()
        ->GetTabStripModel()
        ->GetActiveWebContents()
        ->GetLastCommittedURL();
  }

  // Navigates the browser to the test page indicated by |path|.
  void NavigateToTestPage(const std::string& path) const {
    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server_->GetURL(path)));
  }

  // Executes |script| and stores the result as a string in |result|. A boolean
  // will be returned, indicating whether the script was executed successfully.
  content::EvalJsResult RunScript(const std::string& script) const {
    return content::EvalJs(browser()
                               ->GetTabStripModel()
                               ->GetActiveWebContents()
                               ->GetPrimaryMainFrame(),
                           script);
  }

  GURL TestPageUrl() const {
    return https_server_->GetURL(std::string("/") + kTestFileName);
  }

  content::EvalJsResult RequestAndRespondToPermission(
      permissions::PermissionRequestManager::AutoResponseType bubble_response) {
    std::string result;
    content::WebContents* web_contents = GetActiveWebContents(browser());
    permissions::PermissionRequestManager::FromWebContents(web_contents)
        ->set_auto_response_for_test(bubble_response);
    return RunScript("RequestPermission();");
  }

  content::WebContents* GetActiveWebContents(Browser* browser) {
    return browser->tab_strip_model()->GetActiveWebContents();
  }

  // Show a notification that sets `close_event_timeout` milliseconds in the
  // notification options data.
  void DisplayNotificationWithCloseEventTimeout(
      std::string notification_title,
      base::TimeDelta close_event_timeout,
      std::unique_ptr<message_center::Notification>* displayed_notification) {
    size_t notification_count_before_display =
        GetDisplayedNotifications(true /* is_persistent */).size();

    std::string display_notification_script = base::StringPrintf(
        "DisplayPersistentNotification('%s', {data: { close_event_timeout: "
        "%d}})",
        notification_title, close_event_timeout.InMilliseconds());
    EXPECT_EQ("ok", RunScript(display_notification_script));

    std::vector<message_center::Notification> notifications =
        WaitForDisplayedNotifications(true /* is_persistent */,
                                      notification_count_before_display + 1u);
    ASSERT_EQ(notifications.size(), notification_count_before_display + 1u);

    *displayed_notification =
        std::make_unique<message_center::Notification>(notifications.back());
  }

  // Waits for a message from the 'notificationclose' event handler.
  void WaitForNotificationCloseEvent(std::string expected_notification_title) {
    std::string actual_message_from_worker =
        RunScript("GetMessageFromWorker()").ExtractString();

    std::string expected_message_from_worker = base::StringPrintf(
        "closing notification: %s", expected_notification_title);

    EXPECT_EQ(actual_message_from_worker, expected_message_from_worker);
  }

  const base::FilePath server_root_;
  std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
  base::HistogramTester histogram_tester_;
  base::UserActionTester user_action_tester_;

 private:
  std::unique_ptr<net::EmbeddedTestServer> https_server_;
};

PlatformNotificationServiceBrowserTest::PlatformNotificationServiceBrowserTest()
    : server_root_(FILE_PATH_LITERAL("chrome/test/data")) {}

// TODO(peter): Move PlatformNotificationService-related tests over from
// notification_interactive_uitest.cc to this file.

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       DisplayPersistentNotificationWithPermission) {
  RequestAndAcceptPermission();

  // Expect 0.5 engagement for the navigation.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('action_none')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id(),
      std::nullopt /* action_index */, std::nullopt /* reply */);

  // We expect +1 engagement for the notification interaction.
  EXPECT_DOUBLE_EQ(1.5, GetEngagementScore(GetLastCommittedURL()));

  // Clicking on the notification should not automatically close it.
  notifications = WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  EXPECT_EQ(message_center::FullscreenVisibility::NONE,
            notifications[0].fullscreen_visibility());

  EXPECT_EQ("action_none", RunScript("GetMessageFromWorker()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  // Check UMA was recorded.
  EXPECT_EQ(
      1, user_action_tester_.GetActionCount("Notifications.Persistent.Shown"));
  EXPECT_EQ(1, user_action_tester_.GetActionCount(
                   "Notifications.Persistent.Clicked"));
  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationClickResult",
      0 /* SERVICE_WORKER_OK */, 1);

  histogram_tester_.ExpectUniqueSample(
      "Notifications.Engagement.Displayed.Volume0", 1, 1);
  histogram_tester_.ExpectUniqueSample(
      "Notifications.Engagement.Clicked.Volume0", 1, 1);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       NonPersistentWebNotificationOptionsReflection) {
  GrantNotificationPermissionForTest();

  // First, test the default values.

  EXPECT_EQ("ok", RunScript("DisplayNonPersistentNotification('Title')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(false /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  // We don't use the notification's direction or language, hence we don't check
  // those properties here.
  const message_center::Notification& default_notification = notifications[0];
  EXPECT_EQ("Title", base::UTF16ToUTF8(default_notification.title()));
  EXPECT_EQ("", base::UTF16ToUTF8(default_notification.message()));
  EXPECT_TRUE(default_notification.image().IsEmpty());
  EXPECT_TRUE(default_notification.icon().IsEmpty());
  EXPECT_TRUE(default_notification.small_image().IsEmpty());
  EXPECT_FALSE(default_notification.renotify());
  EXPECT_FALSE(default_notification.silent());
  EXPECT_FALSE(default_notification.never_timeout());
  EXPECT_EQ(0u, default_notification.buttons().size());

  // Verify that the notification's default timestamp is set in the last 30
  // seconds. (30 has no significance, just needs to be significantly high to
  // avoid test flakiness.)
  EXPECT_NEAR(default_notification.timestamp().InSecondsFSinceUnixEpoch(),
              base::Time::Now().InSecondsFSinceUnixEpoch(), 30);

  // Now, test the non-default values.

  EXPECT_EQ("ok", RunScript(
                      R"(DisplayNonPersistentNotification('Title2', {
          body: 'Contents',
          tag: 'replace-id',
          dir: 'rtl',
          lang: 'nl-NL',
          image: 'icon.png',
          icon: 'icon.png',
          badge: 'icon.png',
          timestamp: 621046800000,
          vibrate: [500, 200, 100],
          renotify: true,
          requireInteraction: true,
          data: [
            { property: 'value' }
          ]
        }))"));

  notifications = WaitForDisplayedNotifications(false /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  message_center::Notification notification = notifications[1];
  EXPECT_EQ("Title2", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(notification.message()));

  // The js-provided tag should be part of the id.
  EXPECT_FALSE(notification.id().find("replace-id") == std::string::npos);

  EXPECT_THAT(notification.vibration_pattern(),
              testing::ElementsAreArray({500, 200, 100}));

  EXPECT_TRUE(notification.renotify());
  EXPECT_TRUE(notification.never_timeout());
  EXPECT_DOUBLE_EQ(621046800000.,
                   notification.timestamp().InMillisecondsFSinceUnixEpoch());

#if !BUILDFLAG(IS_MAC)
  EXPECT_FALSE(notification.image().IsEmpty());
  EXPECT_EQ(kIconWidth, notification.image().Width());
  EXPECT_EQ(kIconHeight, notification.image().Height());
#endif

  EXPECT_FALSE(notification.icon().IsEmpty());
  EXPECT_EQ(kIconWidth, notification.icon().Size().width());
  EXPECT_EQ(kIconHeight, notification.icon().Size().height());
  EXPECT_FALSE(notification.small_image().IsEmpty());

  // Test that notifications with the same tag replace each other and have
  // identical ids.

  EXPECT_EQ("ok", RunScript(
                      R"(DisplayNonPersistentNotification('Title3', {
          tag: 'replace-id'
        }))"));

  notifications = WaitForDisplayedNotifications(false /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  message_center::Notification replacement = notifications[1];
  EXPECT_EQ("Title3", base::UTF16ToUTF8(replacement.title()));
  EXPECT_EQ(notification.id(), replacement.id());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       DisplayAndCloseNonPersistentNotification) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok", RunScript("DisplayNonPersistentNotification('Title1')"));

  EXPECT_EQ("ok",
            RunScript("DisplayAndCloseNonPersistentNotification('Title2')"));

  // Check that the first notification is still displayed and no others.
  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(false /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());
  EXPECT_EQ(u"Title1", notifications[0].title());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       WebNotificationOptionsReflection) {
  GrantNotificationPermissionForTest();

  // First, test the default values.

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('Some title', {})"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  // We don't use the notification's direction or language, hence we don't check
  // those properties here.
  const message_center::Notification& default_notification = notifications[0];
  EXPECT_EQ("Some title", base::UTF16ToUTF8(default_notification.title()));
  EXPECT_EQ("", base::UTF16ToUTF8(default_notification.message()));
  EXPECT_TRUE(default_notification.image().IsEmpty());
  EXPECT_TRUE(default_notification.icon().IsEmpty());
  EXPECT_TRUE(default_notification.small_image().IsEmpty());
  EXPECT_FALSE(default_notification.renotify());
  EXPECT_FALSE(default_notification.silent());
  EXPECT_FALSE(default_notification.never_timeout());
  EXPECT_EQ(0u, default_notification.buttons().size());
  EXPECT_EQ(message_center::NotificationScenario::DEFAULT,
            default_notification.scenario());

  // Verifies that the notification's default timestamp is set in the last 30
  // seconds. This number has no significance, but it needs to be significantly
  // high to avoid flakiness in the test.
  EXPECT_NEAR(default_notification.timestamp().InSecondsFSinceUnixEpoch(),
              base::Time::Now().InSecondsFSinceUnixEpoch(), 30);

  // Now, test the non-default values.

  EXPECT_EQ("ok", RunScript("DisplayPersistentAllOptionsNotification()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  // We don't use the notification's direction or language, hence we don't check
  // those properties here.
  const message_center::Notification& all_options_notification =
      notifications[1];
  EXPECT_EQ("Title", base::UTF16ToUTF8(all_options_notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(all_options_notification.message()));
  // The js-provided tag should be part of the id.
  EXPECT_FALSE(all_options_notification.id().find("replace-id") ==
               std::string::npos);
#if !BUILDFLAG(IS_MAC)
  EXPECT_FALSE(all_options_notification.image().IsEmpty());
  EXPECT_EQ(kIconWidth, all_options_notification.image().Width());
  EXPECT_EQ(kIconHeight, all_options_notification.image().Height());
#endif
  EXPECT_FALSE(all_options_notification.icon().IsEmpty());
  EXPECT_EQ(kIconWidth, all_options_notification.icon().Size().width());
  EXPECT_EQ(kIconHeight, all_options_notification.icon().Size().height());
  EXPECT_FALSE(all_options_notification.small_image().IsEmpty());
  EXPECT_TRUE(all_options_notification.renotify());
  EXPECT_TRUE(all_options_notification.silent());
  EXPECT_TRUE(all_options_notification.never_timeout());
  EXPECT_DOUBLE_EQ(
      kNotificationTimestamp,
      all_options_notification.timestamp().InMillisecondsFSinceUnixEpoch());
  EXPECT_EQ(1u, all_options_notification.buttons().size());
  EXPECT_EQ("actionTitle",
            base::UTF16ToUTF8(all_options_notification.buttons()[0].title));
  EXPECT_FALSE(all_options_notification.buttons()[0].icon.IsEmpty());
  EXPECT_EQ(kIconWidth, all_options_notification.buttons()[0].icon.Width());
  EXPECT_EQ(kIconHeight, all_options_notification.buttons()[0].icon.Height());
  EXPECT_EQ(message_center::ButtonType::DEFAULT,
            all_options_notification.buttons()[0].type);
}

// Chrome OS shows the notification settings inline.
#if !BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       WebNotificationSiteSettingsButton) {
  GrantNotificationPermissionForTest();

  // Expect 0.5 engagement for the navigation.
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  GURL origin = web_contents->GetLastCommittedURL();
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(origin));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('Some title', {})"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());
  EXPECT_EQ(0u, notifications[0].buttons().size());

  display_service_tester_->SimulateSettingsClick(
      NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id());

  // Clicking on the settings button should not close the notification.
  notifications = WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  web_contents = browser()->GetTabStripModel()->GetActiveWebContents();
  // We see some timeouts in dbg tests, so increase the wait timeout to the
  // test launcher's timeout.
  const base::test::ScopedRunLoopTimeout specific_timeout(
      FROM_HERE, TestTimeouts::test_launcher_timeout());
  ASSERT_TRUE(content::WaitForLoadStop(web_contents));

  // No engagement should be granted for clicking on the settings link.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(origin));

  std::string url = web_contents->GetLastCommittedURL().spec();
  ASSERT_EQ(content::GetWebUIURLString("settings/content/notifications"), url);
}
#endif

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       WebNotificationOptionsVibrationPattern) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotificationVibrate()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  const message_center::Notification& notification = notifications[0];
  EXPECT_EQ("Title", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(notification.message()));

  EXPECT_THAT(notification.vibration_pattern(),
              testing::ElementsAreArray(kNotificationVibrationPattern));
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       CloseDisplayedPersistentNotification) {
  GrantNotificationPermissionForTest();

  // Expect 0.5 engagement for the navigation.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('action_close')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  {
    base::RunLoop notification_closed_run_loop;
    display_service_tester_->SetNotificationClosedClosure(
        notification_closed_run_loop.QuitClosure());

    display_service_tester_->SimulateClick(
        NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id(),
        std::nullopt /* action_index */, std::nullopt /* reply */);

    // We have interacted with the button, so expect a notification bump.
    EXPECT_DOUBLE_EQ(1.5, GetEngagementScore(GetLastCommittedURL()));

    EXPECT_EQ("action_close", RunScript("GetMessageFromWorker()"));

    notification_closed_run_loop.Run();
  }

  notifications = GetDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(0u, notifications.size());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       UserClosesPersistentNotification) {
  GrantNotificationPermissionForTest();

  // Expect 0.5 engagement for the navigation.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('close_test')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  display_service_tester_->RemoveNotification(
      NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id(),
      true /* by_user */);

  // The user closed this notification so the score should remain the same.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("closing notification: close_test",
            RunScript("GetMessageFromWorker()"));

  EXPECT_EQ(1, user_action_tester_.GetActionCount(
                   "Notifications.Persistent.ClosedByUser"));
  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      0 /* SERVICE_WORKER_OK */, 1);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       BlockingPermissionClosesNotification) {
  GrantNotificationPermissionForTest();

  // Creates a simple notification.
  ASSERT_EQ("ok", RunScript("DisplayPersistentNotification()"));
  ASSERT_EQ(1u, WaitForDisplayedNotifications(true /* is_persistent */).size());

  // Block permissions and wait until notification got closed.
  base::RunLoop run_loop;
  display_service_tester_->SetNotificationClosedClosure(run_loop.QuitClosure());
  BlockNotificationPermissionForTest();
  run_loop.Run();

  // Notification should be closed after blocking permissions.
  ASSERT_EQ(0u, GetDisplayedNotifications(true /* is_persistent */).size());

  // We are still in the process of closing the notification, but the recording
  // of the number of closed notifications happens after that. Run loop until
  // that is done to verify the UMA entry.
  base::RunLoop().RunUntilIdle();
  histogram_tester_.ExpectBucketCount(
      "Notifications.Permissions.RevokeDeleteCount",
      1 /* deleted_notifications */, 1 /* sample_count */);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       CloseAndUserClosePersistentNotificationWithTag) {
  GrantNotificationPermissionForTest();
  {
    EXPECT_EQ("ok", RunScript(
                        R"(DisplayPersistentNotification('action_close', {
            tag: 'tag-1'
        }))"));

    std::vector<message_center::Notification> notifications =
        WaitForDisplayedNotifications(true /* is_persistent */);
    ASSERT_EQ(1u, notifications.size());

    base::RunLoop run_loop;
    display_service_tester_->SetNotificationClosedClosure(
        run_loop.QuitClosure());

    display_service_tester_->SimulateClick(
        NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id(),
        std::nullopt /* action_index */, std::nullopt /* reply */);

    EXPECT_EQ("action_close", RunScript("GetMessageFromWorker()"));
    run_loop.Run();

    // Clear the closure to prevent an UAF when the notification is closed in
    // the next set of test steps.
    display_service_tester_->SetNotificationClosedClosure(
        base::RepeatingClosure());
  }
  {
    EXPECT_EQ("ok", RunScript(
                        R"(DisplayPersistentNotification('close_test', {
            tag: 'tag-1'
        }))"));

    std::vector<message_center::Notification> notifications =
        WaitForDisplayedNotifications(true /* is_persistent */);
    ASSERT_EQ(1u, notifications.size());

    display_service_tester_->RemoveNotification(
        NotificationHandler::Type::WEB_PERSISTENT, notifications[0].id(),
        true /* by_user */);

    EXPECT_EQ("closing notification: close_test",
              RunScript("GetMessageFromWorker()"));
  }
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       TestDisplayOriginContextMessage) {
  RequestAndAcceptPermission();

  // Creates a simple notification.
  ASSERT_EQ("ok", RunScript("DisplayPersistentNotification()"));

  GURL test_origin = TestPageUrl().DeprecatedGetOriginAsURL();

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  EXPECT_TRUE(notifications[0].context_message().empty());
  EXPECT_EQ(test_origin.spec(), notifications[0].origin_url().spec());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       PersistentNotificationServiceWorkerScope) {
  RequestAndAcceptPermission();

  // Creates a simple notification.
  ASSERT_EQ("ok", RunScript("DisplayPersistentNotification()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  EXPECT_EQ(
      TestPageUrl(),
      PersistentNotificationMetadata::From(
          display_service_tester_->GetMetadataForNotification(notifications[0]))
          ->service_worker_scope);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       DataUrlAsNotificationImage) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotificationDataUrlImage()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  const message_center::Notification& notification = notifications[0];
  EXPECT_FALSE(notification.icon().IsEmpty());

  EXPECT_EQ("Data URL Title", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ(kIconWidth, notification.icon().Size().width());
  EXPECT_EQ(kIconHeight, notification.icon().Size().height());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       BlobAsNotificationImage) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotificationBlobImage()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  const message_center::Notification& notification = notifications[0];
  EXPECT_FALSE(notification.icon().IsEmpty());

  EXPECT_EQ("Blob Title", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ(kIconWidth, notification.icon().Size().width());
  EXPECT_EQ(kIconHeight, notification.icon().Size().height());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       DisplayPersistentNotificationWithActionButtons) {
  GrantNotificationPermissionForTest();

  // Expect 0.5 engagement for the navigation.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("ok",
            RunScript("DisplayPersistentNotificationWithActionButtons()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  const message_center::Notification& notification = notifications[0];
  ASSERT_EQ(2u, notification.buttons().size());
  EXPECT_EQ("actionTitle1", base::UTF16ToUTF8(notification.buttons()[0].title));
  EXPECT_EQ("actionTitle2", base::UTF16ToUTF8(notification.buttons()[1].title));

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::WEB_PERSISTENT, notification.id(),
      0 /* action_index */, std::nullopt /* reply */);

  EXPECT_EQ("action_button_click actionId1",
            RunScript("GetMessageFromWorker()"));
  EXPECT_DOUBLE_EQ(1.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ(1, user_action_tester_.GetActionCount(
                   "Notifications.Persistent.ClickedActionButton"));
  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationClickResult",
      0 /* SERVICE_WORKER_OK */, 1);

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::WEB_PERSISTENT, notification.id(),
      1 /* action_index */, std::nullopt /* reply */);

  EXPECT_EQ("action_button_click actionId2",
            RunScript("GetMessageFromWorker()"));
  EXPECT_DOUBLE_EQ(2.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ(2, user_action_tester_.GetActionCount(
                   "Notifications.Persistent.ClickedActionButton"));
  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationClickResult",
      0 /* SERVICE_WORKER_OK */, 2);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       DisplayPersistentNotificationWithReplyButton) {
  GrantNotificationPermissionForTest();

  // Expect 0.5 engagement for the navigation.
  EXPECT_DOUBLE_EQ(0.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotificationWithReplyButton()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  const message_center::Notification& notification = notifications[0];
  ASSERT_EQ(1u, notification.buttons().size());
  EXPECT_EQ("actionTitle1", base::UTF16ToUTF8(notification.buttons()[0].title));

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::WEB_PERSISTENT, notification.id(),
      0 /* action_index */, u"hello");

  EXPECT_EQ("action_button_click actionId1 hello",
            RunScript("GetMessageFromWorker()"));
  EXPECT_DOUBLE_EQ(1.5, GetEngagementScore(GetLastCommittedURL()));

  EXPECT_EQ(1, user_action_tester_.GetActionCount(
                   "Notifications.Persistent.ClickedActionButton"));
  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationClickResult",
      0 /* SERVICE_WORKER_OK */, 1);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       GetDisplayedNotifications) {
  RequestAndAcceptPermission();

  EXPECT_EQ("ok",
            RunScript("DisplayNonPersistentNotification('NonPersistent')"));
  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('PersistentI')"));
  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('PersistentII')"));

  // Only the persistent ones should show.
  EXPECT_EQ("ok", RunScript("GetDisplayedNotifications()"));

  std::string script_message =
      RunScript("GetMessageFromWorker()").ExtractString();

  std::vector<std::string> notification_ids = base::SplitString(
      script_message, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
  ASSERT_EQ(2u, notification_ids.size());

  const std::string first_id = notification_ids[0];

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  // Now remove one of the notifications straight from the ui manager
  // without going through the database.
  const message_center::Notification& notification = notifications[1];

  // p# is the prefix for persistent notifications. See
  //  content/browser/notifications/notification_id_generator.{h,cc} for details
  ASSERT_TRUE(
      base::StartsWith(notification.id(), "p#", base::CompareCase::SENSITIVE));

  display_service_tester_->RemoveNotification(
      NotificationHandler::Type::WEB_PERSISTENT, notification.id(),
      false /* by_user */, true /* silent */);

  EXPECT_EQ("ok", RunScript("GetDisplayedNotifications()"));

  script_message = RunScript("GetMessageFromWorker()").ExtractString();
  notification_ids = base::SplitString(
      script_message, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);

  // The list of displayed notification Ids should have been updated.
  ASSERT_EQ(1u, notification_ids.size());
  ASSERT_EQ(notification_ids[0], first_id);
}

// TODO(crbug.com/40098231): Test is flaky on TSAN.
#if defined(THREAD_SANITIZER)
#define MAYBE_OrphanedNonPersistentNotificationCreatesForegroundTab \
  DISABLED_OrphanedNonPersistentNotificationCreatesForegroundTab
#else
#define MAYBE_OrphanedNonPersistentNotificationCreatesForegroundTab \
  OrphanedNonPersistentNotificationCreatesForegroundTab
#endif
IN_PROC_BROWSER_TEST_F(
    PlatformNotificationServiceBrowserTest,
    MAYBE_OrphanedNonPersistentNotificationCreatesForegroundTab) {
  // Verifies that activating a non-persistent notification that no longer has
  // any event listeners attached (e.g. because the tab closed) creates a new
  // foreground tab.

  Profile* profile = browser()->GetProfile();

  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(profile);
  ASSERT_TRUE(display_service);

  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_NON_PERSISTENT);
  ASSERT_TRUE(handler);

  // There should be one open tab for the current |browser()|.
  EXPECT_EQ(browser()->GetTabStripModel()->count(), 1);
  content::WebContents* original_web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Signal an activation for a notification that's never been shown.
  {
    base::RunLoop run_loop;
    handler->OnClick(profile, GURL(kTestNotificationOrigin),
                     kTestNotificationId, std::nullopt /* action_index */,
                     std::nullopt /* reply */, run_loop.QuitClosure());
    run_loop.Run();
  }

  // A second tab should've been created and have been brought to the foreground
  // for the notification's test origin.
  ASSERT_EQ(browser()->GetTabStripModel()->count(), 2);
  content::WebContents* active_web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  EXPECT_NE(active_web_contents, original_web_contents);
  EXPECT_EQ(active_web_contents->GetVisibleURL(),
            GURL(kTestNotificationOrigin));
}

// Mac OS X exclusively uses native notifications, so the decision on whether to
// display notifications whilst fullscreen is deferred to the operating system.
#if !BUILDFLAG(IS_MAC)

// TODO(crbug.com/40132496) Test is flaky on Linux TSan.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_TestShouldDisplayFullscreen DISABLED_TestShouldDisplayFullscreen
#else
#define MAYBE_TestShouldDisplayFullscreen TestShouldDisplayFullscreen
#endif
IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       MAYBE_TestShouldDisplayFullscreen) {
  GrantNotificationPermissionForTest();

  // Set the page fullscreen
  ui_test_utils::ToggleFullscreenModeAndWait(browser());

  ASSERT_TRUE(ui_test_utils::ShowAndFocusNativeWindow(
      browser()->GetWindow()->GetNativeWindow()));

  ui_test_utils::BrowserActivationWaiter(browser()).WaitForActivation();
  ASSERT_TRUE(browser()->GetWindow()->IsActive())
      << "Browser is active after going fullscreen";

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('display_normal')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  EXPECT_EQ(message_center::FullscreenVisibility::OVER_USER,
            notifications[0].fullscreen_visibility());
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       TestShouldDisplayMultiFullscreen) {
  ASSERT_NO_FATAL_FAILURE(GrantNotificationPermissionForTest());

  BrowserWindowInterface* other_browser =
      CreateBrowser(browser()->GetProfile());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(other_browser, GURL("about:blank")));

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('display_normal')"));

  // Set the notifcation page fullscreen
  ui_test_utils::ToggleFullscreenModeAndWait(browser());

  // Set the other browser fullscreen
  ui_test_utils::ToggleFullscreenModeAndWait(other_browser);

  ASSERT_TRUE(browser()
                  ->GetFeatures()
                  .exclusive_access_manager()
                  ->context()
                  ->IsFullscreen());
  ASSERT_TRUE(other_browser->GetFeatures()
                  .exclusive_access_manager()
                  ->context()
                  ->IsFullscreen());

  ui_test_utils::BrowserActivationWaiter(other_browser).WaitForActivation();
  ASSERT_FALSE(browser()->GetWindow()->IsActive());
  ASSERT_TRUE(other_browser->GetWindow()->IsActive());

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  EXPECT_EQ(message_center::FullscreenVisibility::NONE,
            notifications[0].fullscreen_visibility());
}

#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       KeepAliveRegistryPendingNotificationClickEvent) {
  RequestAndAcceptPermission();

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('action_none')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  ASSERT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT));

  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop run_loop;
  handler->OnClick(browser()->GetProfile(), notifications[0].origin_url(),
                   notifications[0].id(), std::nullopt /* action_index */,
                   std::nullopt /* reply */, run_loop.QuitClosure());

  // The asynchronous part of the click event will still be in progress, but
  // the keep alive registration should have been created.
  ASSERT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT));

  // Finish the click event.
  run_loop.Run();

  ASSERT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT));
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       KeepAliveRegistryPendingNotificationCloseEvent) {
  RequestAndAcceptPermission();

  EXPECT_EQ("ok", RunScript("DisplayPersistentNotification('action_none')"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  ASSERT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop run_loop;
  handler->OnClose(browser()->GetProfile(), notifications[0].origin_url(),
                   notifications[0].id(), true /* by_user */,
                   run_loop.QuitClosure());

  // The asynchronous part of the close event will still be in progress, but
  // the keep alive registration should have been created.
  ASSERT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Finish the close event.
  run_loop.Run();

  ASSERT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));
}

// TODO(crbug.com/430163317): Re-enable this test
#if BUILDFLAG(IS_MAC)
#define MAYBE_CloseBrowserDuringNotificationCloseEvent \
  DISABLED_CloseBrowserDuringNotificationCloseEvent
#else
#define MAYBE_CloseBrowserDuringNotificationCloseEvent \
  CloseBrowserDuringNotificationCloseEvent
#endif
IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       MAYBE_CloseBrowserDuringNotificationCloseEvent) {
  RequestAndAcceptPermission();

  // Show a notification with a close event that will take 3 seconds to
  // complete.
  std::unique_ptr<message_center::Notification> notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      kTestNotificationTitle, /*close_event_timeout=*/base::Seconds(3),
      &notification));

  // Verify initial state with no in progress close events.
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectTotalCount(
      "Notifications.PersistentWebNotificationCloseResult", 0);

  // Close the notification.
  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop close_event_run_loop;
  handler->OnClose(browser()->GetProfile(), notification->origin_url(),
                   notification->id(), /*by_user=*/true,
                   close_event_run_loop.QuitClosure());

  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Wait for the service worker to run the 'notificationclose' event.
  WaitForNotificationCloseEvent(/*expected_title=*/kTestNotificationTitle);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Close the browser before the 'notificationclose' event completes.
  CloseBrowserSynchronously(browser());

  // Wait for the 'notificationclose' event to complete.
  close_event_run_loop.Run();
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      content::PersistentNotificationStatus::kSuccess, 1);
}

// TODO(crbug.com/430163317): Re-enable this test
#if BUILDFLAG(IS_MAC)
#define MAYBE_NotificationCloseEventAfterBrowserClosed \
  DISABLED_NotificationCloseEventAfterBrowserClosed
#else
#define MAYBE_NotificationCloseEventAfterBrowserClosed \
  NotificationCloseEventAfterBrowserClosed
#endif
IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       MAYBE_NotificationCloseEventAfterBrowserClosed) {
  RequestAndAcceptPermission();

  // Show two notifications.  The first one will have a close event that takes 3
  // seconds to complete.  The second one will have a close event that completes
  // immediately.
  std::unique_ptr<message_center::Notification> first_notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      kTestNotificationTitle, /*close_event_timeout=*/base::Seconds(3),
      &first_notification));

  std::unique_ptr<message_center::Notification> second_notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      "fake_title_2", /*close_event_timeout=*/base::Seconds(0),
      &second_notification));

  // Verify initial state with no in progress close events.
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectTotalCount(
      "Notifications.PersistentWebNotificationCloseResult", 0);

  // Close the first notification.
  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop first_close_event_run_loop;
  handler->OnClose(browser()->GetProfile(), first_notification->origin_url(),
                   first_notification->id(), /*by_user=*/true,
                   first_close_event_run_loop.QuitClosure());
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Wait for the service worker to run the 'notificationclose' event for the
  // first notification.
  WaitForNotificationCloseEvent(/*expected_title=*/kTestNotificationTitle);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Close the browser before the first 'notificationclose' event completes.
  Profile* profile = browser()->GetProfile();
  CloseBrowserSynchronously(browser());

  // Close the second notification.
  base::RunLoop second_close_event_run_loop;
  handler->OnClose(profile, second_notification->origin_url(),
                   second_notification->id(), /*by_user=*/true,
                   second_close_event_run_loop.QuitClosure());

  // Wait for both 'notificationclose' events to complete.
  first_close_event_run_loop.Run();
  second_close_event_run_loop.Run();

  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      content::PersistentNotificationStatus::kSuccess, 2);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       NotificationCloseEventAfterShutdownStarting) {
  base::HistogramTester histogram_tester_;
  RequestAndAcceptPermission();

  // Show a notification with a close event that will timeout unless canceled.
  std::unique_ptr<message_center::Notification> notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      kTestNotificationTitle, /*close_event_timeout=*/base::Days(1),
      &notification));

  // Verify initial state with no in progress close events.
  ASSERT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectTotalCount(
      "Notifications.PersistentWebNotificationCloseResult", 0);

  // Simulate browser shutdown.
  browser_shutdown::OnShutdownStarting(
      browser_shutdown::ShutdownType::kEndSession);

  // Close the notification.
  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop close_event_run_loop;
  handler->OnClose(browser()->GetProfile(), notification->origin_url(),
                   notification->id(), /*by_user=*/true,
                   close_event_run_loop.QuitClosure());

  // Wait for the close event to complete.  Starting browser shutdown must
  // prevent the 'notificationclose' event from running.
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));
  close_event_run_loop.Run();

  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      content::PersistentNotificationStatus::kCanceledByAppTerminating, 1);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       OpenBrowserDuringNotificationCloseEvent) {
  RequestAndAcceptPermission();

  // Show a notification with a close event that will take 3 seconds to
  // complete.
  std::unique_ptr<message_center::Notification> notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      kTestNotificationTitle, /*close_event_timeout=*/base::Seconds(3),
      &notification));

  // Verify initial state with no in progress close events.
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectTotalCount(
      "Notifications.PersistentWebNotificationCloseResult", 0);

  // Close the notification.
  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop close_event_run_loop;
  handler->OnClose(browser()->GetProfile(), notification->origin_url(),
                   notification->id(), /*by_user=*/true,
                   close_event_run_loop.QuitClosure());

  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Wait for the service worker to run the 'notificationclose' event.
  WaitForNotificationCloseEvent(/*expected_title=*/kTestNotificationTitle);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Close the browser before the 'notificationclose' event completes.
  Profile* profile = browser()->GetProfile();
  CloseBrowserSynchronously(browser());

  // Reopen the browser before the 'notificationclose' event completes.
  BrowserWindowInterface* second_browser = CreateBrowser(profile);
  EXPECT_TRUE(ui_test_utils::NavigateToURL(second_browser, TestPageUrl()));

  // Wait for the 'notificationclose' event to complete.
  close_event_run_loop.Run();
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      content::PersistentNotificationStatus::kSuccess, 1);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceBrowserTest,
                       TryToQuitDuringNotificationCloseEvent) {
  RequestAndAcceptPermission();

  // Show a notification with a close event that will timeout unless canceled.
  std::unique_ptr<message_center::Notification> notification;
  ASSERT_NO_FATAL_FAILURE(DisplayNotificationWithCloseEventTimeout(
      kTestNotificationTitle, /*close_event_timeout=*/base::Days(1),
      &notification));

  // Verify initial state with no in progress close events.
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectTotalCount(
      "Notifications.PersistentWebNotificationCloseResult", 0);

  // Close the notification.
  NotificationDisplayServiceImpl* display_service =
      NotificationDisplayServiceImpl::GetForProfile(browser()->GetProfile());
  NotificationHandler* handler = display_service->GetNotificationHandler(
      NotificationHandler::Type::WEB_PERSISTENT);
  ASSERT_TRUE(handler);

  base::RunLoop close_event_run_loop;
  handler->OnClose(browser()->GetProfile(), notification->origin_url(),
                   notification->id(), /*by_user=*/true,
                   close_event_run_loop.QuitClosure());

  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Wait for the service worker to run the 'notificationclose' event.
  WaitForNotificationCloseEvent(/*expected_title=*/kTestNotificationTitle);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  // Indicate that the browser should start shutdown after the browser window
  // closes, ignoring any other type of keep alives like
  // `PENDING_NOTIFICATION_CLOSE_EVENT`.
  browser_shutdown::SetTryingToQuit(true);

  // Close the browser before the 'notificationclose' event completes.
  CloseBrowserSynchronously(browser());

  // Wait for the close event to complete.  Closing the browser must run all app
  // terminating callbacks, including `PushMessagingServiceImpl::Shutdown()`,
  // which releases `PENDING_NOTIFICATION_CLOSE_EVENT` keep alives immediately
  // without waiting for the service worker 'notificationclose' event to
  // complete.
  close_event_run_loop.Run();

  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT));

  histogram_tester_.ExpectUniqueSample(
      "Notifications.PersistentWebNotificationCloseResult",
      content::PersistentNotificationStatus::kCanceledByAppTerminating, 1);
}

#endif  // BUILDFLAG(ENABLE_BACKGROUND_MODE)

class PlatformNotificationServiceIncomingCallTest
    : public PlatformNotificationServiceBrowserTest {
 public:
  // InProcessBrowserTest overrides.
  void SetUpInProcessBrowserTestFixture() override {
    scoped_feature_list_.InitWithFeatures(
        {features::kIncomingCallNotifications}, {});
  }

 private:
  web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceIncomingCallTest,
                       DisplayIncomingCallNotificationWithActionButtons) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok",
            RunScript("DisplayIncomingCallNotificationWithActionButton()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  // When sent from an origin that does not have an installed web app, the
  // scenario should be set to DEFAULT and the default dismiss button should be
  // present.
  const message_center::Notification& notification = notifications[0];
  EXPECT_EQ("Title", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::DEFAULT,
            notification.scenario());
  ASSERT_EQ(2u, notification.buttons().size());
  EXPECT_EQ("actionTitle1", base::UTF16ToUTF8(notification.buttons()[0].title));
  EXPECT_EQ(message_center::ButtonType::ACKNOWLEDGE,
            notification.buttons()[0].type);
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            notification.buttons()[1].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            notification.buttons()[1].type);

  // Install the web app.
  const GURL web_app_url = TestPageUrl();
  const webapps::AppId app_id = web_app::test::InstallDummyWebApp(
      browser()->GetProfile(), "Web App Title", web_app_url);

  EXPECT_EQ("ok",
            RunScript("DisplayIncomingCallNotificationWithActionButton()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  // After installing the origin's web app, the scenario is set to
  // INCOMING_CALL.
  const message_center::Notification& app_notification = notifications[1];
  EXPECT_EQ("Title", base::UTF16ToUTF8(app_notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(app_notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::INCOMING_CALL,
            app_notification.scenario());
  ASSERT_EQ(2u, app_notification.buttons().size());
  EXPECT_EQ("actionTitle1",
            base::UTF16ToUTF8(app_notification.buttons()[0].title));
  EXPECT_EQ(message_center::ButtonType::ACKNOWLEDGE,
            app_notification.buttons()[0].type);
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            app_notification.buttons()[1].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            app_notification.buttons()[1].type);

  web_app::test::UninstallWebApp(browser()->GetProfile(), app_id);

  EXPECT_EQ("ok",
            RunScript("DisplayIncomingCallNotificationWithActionButton()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */, 3u);
  ASSERT_EQ(3u, notifications.size());

  // After uninstalling the origin's web app, the scenario should be set
  // back to DEFAULT.
  const message_center::Notification& uninstall_notification = notifications[2];
  EXPECT_EQ("Title", base::UTF16ToUTF8(uninstall_notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(uninstall_notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::DEFAULT,
            uninstall_notification.scenario());
  ASSERT_EQ(2u, uninstall_notification.buttons().size());
  EXPECT_EQ("actionTitle1",
            base::UTF16ToUTF8(uninstall_notification.buttons()[0].title));
  EXPECT_EQ(message_center::ButtonType::ACKNOWLEDGE,
            uninstall_notification.buttons()[0].type);
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            uninstall_notification.buttons()[1].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            uninstall_notification.buttons()[1].type);
}

IN_PROC_BROWSER_TEST_F(PlatformNotificationServiceIncomingCallTest,
                       DisplayIncomingCallNotificationWithoutActionButtons) {
  GrantNotificationPermissionForTest();

  EXPECT_EQ("ok", RunScript("DisplayIncomingCallNotification()"));

  std::vector<message_center::Notification> notifications =
      WaitForDisplayedNotifications(true /* is_persistent */);
  ASSERT_EQ(1u, notifications.size());

  // When sent from an origin that does not have an installed web app, the
  // scenario should be set to DEFAULT and the default dismiss button should be
  // present, even though no action button was supplied by the origin.
  const message_center::Notification& notification = notifications[0];
  EXPECT_EQ("Title", base::UTF16ToUTF8(notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::DEFAULT,
            notification.scenario());
  ASSERT_EQ(1u, notification.buttons().size());
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            notification.buttons()[0].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            notification.buttons()[0].type);

  // Install the web app.
  const GURL web_app_url = TestPageUrl();
  const webapps::AppId app_id = web_app::test::InstallDummyWebApp(
      browser()->GetProfile(), "Web App Title", web_app_url);

  EXPECT_EQ("ok", RunScript("DisplayIncomingCallNotification()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */, 2u);
  ASSERT_EQ(2u, notifications.size());

  // After installing the origin's web app, the scenario is set to
  // INCOMING_CALL and the default dismiss button should be
  // present, even though no action button was supplied by the origin.
  const message_center::Notification& app_notification = notifications[1];
  EXPECT_EQ("Title", base::UTF16ToUTF8(app_notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(app_notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::INCOMING_CALL,
            app_notification.scenario());
  ASSERT_EQ(1u, app_notification.buttons().size());
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            app_notification.buttons()[0].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            app_notification.buttons()[0].type);

  web_app::test::UninstallWebApp(browser()->GetProfile(), app_id);

  EXPECT_EQ("ok", RunScript("DisplayIncomingCallNotification()"));

  notifications = WaitForDisplayedNotifications(true /* is_persistent */, 3u);
  ASSERT_EQ(3u, notifications.size());

  // After uninstalling the origin's web app, the scenario should be set
  // back to DEFAULT and the default dismiss button should be
  // present, even though no action button was supplied by the origin.
  const message_center::Notification& uninstall_notification = notifications[2];
  EXPECT_EQ("Title", base::UTF16ToUTF8(uninstall_notification.title()));
  EXPECT_EQ("Contents", base::UTF16ToUTF8(uninstall_notification.message()));
  EXPECT_EQ(message_center::NotificationScenario::DEFAULT,
            uninstall_notification.scenario());
  ASSERT_EQ(1u, uninstall_notification.buttons().size());
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_CLOSE),
            uninstall_notification.buttons()[0].title);
  EXPECT_EQ(message_center::ButtonType::DISMISS,
            uninstall_notification.buttons()[0].type);
}
