910e62b5创建于 1月15日历史提交
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "content/browser/notifications/notification_trigger_constants.h"
#include "content/browser/notifications/platform_notification_context_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/mock_platform_notification_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using base::Time;

namespace content {

namespace {

// Fake Service Worker registration id to use in tests requiring one.
const int64_t kFakeServiceWorkerRegistrationId = 42;

}  // namespace

class PlatformNotificationContextTriggerTest : public ::testing::Test {
 public:
  PlatformNotificationContextTriggerTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        success_(false) {
    browser_context_.SetPlatformNotificationService(
        std::make_unique<MockPlatformNotificationService>(&browser_context_));
  }

  void SetUp() override {
    platform_notification_context_ =
        base::MakeRefCounted<PlatformNotificationContextImpl>(
            base::FilePath(), &browser_context_, nullptr);
    platform_notification_context_->SetTaskRunnerForTesting(
        base::SingleThreadTaskRunner::GetCurrentDefault());
    platform_notification_context_->Initialize();
    base::RunLoop().RunUntilIdle();
  }

  void TearDown() override {
    // Destroy the context and allow its background tasks to run to close the
    // database.
    platform_notification_context_.reset();
    task_environment_.RunUntilIdle();
  }

  // Callback to provide when writing a notification to the database.
  void DidWriteNotificationData(bool success,
                                const std::string& notification_id) {
    success_ = success;
  }

 protected:
  void WriteNotificationData(const std::string& tag,
                             std::optional<base::Time> timestamp) {
    ASSERT_TRUE(
        TryWriteNotificationData("https://example.com", tag, timestamp));
  }

  bool TryWriteNotificationData(const std::string& url,
                                const std::string& tag,
                                std::optional<base::Time> timestamp) {
    GURL origin(url);
    NotificationDatabaseData notification_database_data;
    notification_database_data.origin = origin;
    notification_database_data.service_worker_registration_id =
        kFakeServiceWorkerRegistrationId;
    notification_database_data.notification_data.show_trigger_timestamp =
        timestamp;
    notification_database_data.notification_data.tag = tag;

    platform_notification_context_->WriteNotificationData(
        next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
        origin, notification_database_data,
        base::BindOnce(
            &PlatformNotificationContextTriggerTest::DidWriteNotificationData,
            base::Unretained(this)));
    base::RunLoop().RunUntilIdle();
    return success_;
  }

  // Gets the currently displayed notifications from |browser_context_|
  // synchronously.
  std::set<std::string> GetDisplayedNotifications() {
    std::set<std::string> displayed_notification_ids;
    base::RunLoop run_loop;
    browser_context_.GetPlatformNotificationService()
        ->GetDisplayedNotifications(base::BindLambdaForTesting(
            [&](std::set<std::string> notification_ids, bool supports_sync) {
              displayed_notification_ids = std::move(notification_ids);
              run_loop.Quit();
            }));
    run_loop.Run();
    return displayed_notification_ids;
  }

  void TriggerNotifications() {
    platform_notification_context_->TriggerNotifications();
    base::RunLoop().RunUntilIdle();
  }

  BrowserTaskEnvironment task_environment_;  // Must be first member

 private:
  TestBrowserContext browser_context_;
  scoped_refptr<PlatformNotificationContextImpl> platform_notification_context_;

  // Returns the next persistent notification id for tests.
  int64_t next_persistent_notification_id() {
    return next_persistent_notification_id_++;
  }

  bool success_;
  int64_t next_persistent_notification_id_ = 1;
};

TEST_F(PlatformNotificationContextTriggerTest, TriggerInFuture) {
  WriteNotificationData("1", Time::Now() + base::Seconds(10));
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  // Wait until the trigger timestamp is reached.
  task_environment_.FastForwardBy(base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest, TriggerInPast) {
  // Trigger timestamp in the past should immediately trigger.
  WriteNotificationData("1", Time::Now() - base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest,
       OverwriteExistingTriggerInFuture) {
  WriteNotificationData("1", Time::Now() + base::Seconds(10));
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  task_environment_.FastForwardBy(base::Seconds(5));
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  // Overwrites the scheduled notifications with a new trigger timestamp.
  WriteNotificationData("1", Time::Now() + base::Seconds(10));

  task_environment_.FastForwardBy(base::Seconds(5));
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  task_environment_.FastForwardBy(base::Seconds(5));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest, OverwriteExistingTriggerToPast) {
  WriteNotificationData("1", Time::Now() + base::Seconds(10));
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  task_environment_.FastForwardBy(base::Seconds(5));

  // Overwrites the scheduled notifications with a new trigger timestamp.
  WriteNotificationData("1", Time::Now() - base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest,
       OverwriteDisplayedNotificationToPast) {
  WriteNotificationData("1", Time::Now() + base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(10));

  // Overwrites a displayed notification with a trigger timestamp in the past.
  WriteNotificationData("1", Time::Now() - base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest,
       OverwriteDisplayedNotificationToFuture) {
  WriteNotificationData("1", Time::Now() + base::Seconds(10));
  task_environment_.FastForwardBy(base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  // Overwrites a displayed notification which hides it until the trigger
  // timestamp is reached.
  WriteNotificationData("1", Time::Now() + base::Seconds(10));

  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  task_environment_.FastForwardBy(base::Seconds(10));

  // This gets called by the notification scheduling system.
  TriggerNotifications();

  ASSERT_EQ(1u, GetDisplayedNotifications().size());
}

TEST_F(PlatformNotificationContextTriggerTest,
       LimitsNumberOfScheduledNotificationsPerOrigin) {
  for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
    WriteNotificationData(base::NumberToString(i),
                          Time::Now() + base::Seconds(i));
  }

  ASSERT_FALSE(TryWriteNotificationData(
      "https://example.com",
      base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
      Time::Now() +
          base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));

  ASSERT_TRUE(TryWriteNotificationData(
      "https://example2.com",
      base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
      Time::Now() +
          base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}

TEST_F(PlatformNotificationContextTriggerTest, EnforcesLimitOnUpdate) {
  for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
    WriteNotificationData(base::NumberToString(i),
                          Time::Now() + base::Seconds(i));
  }

  ASSERT_TRUE(TryWriteNotificationData(
      "https://example.com",
      base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
      std::nullopt));

  ASSERT_FALSE(TryWriteNotificationData(
      "https://example.com",
      base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
      Time::Now() +
          base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}

TEST_F(PlatformNotificationContextTriggerTest, RecordDisplayDelay) {
  base::HistogramTester histogram_tester;
  base::TimeDelta trigger_delay = base::Seconds(10);
  base::TimeDelta display_delay = base::Seconds(8);

  WriteNotificationData("1", Time::Now() + trigger_delay);
  ASSERT_EQ(0u, GetDisplayedNotifications().size());

  // Forward time until after the expected trigger time.
  task_environment_.FastForwardBy(trigger_delay + display_delay);

  // Trigger notification |display_delay| after it should have been displayed.
  TriggerNotifications();
}

}  // namespace content