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 "content/browser/devtools/devtools_background_services_context_impl.h"

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

#include "base/containers/flat_map.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "content/browser/devtools/devtools_background_services.pb.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/public/browser/content_browser_client.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/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
#include "url/origin.h"

namespace content {
namespace {

using testing::_;

const std::string kEventName = "Test Event";
const std::string kInstanceId = "my-instance";

class TestBrowserClient : public ContentBrowserClient {
 public:
  TestBrowserClient() {}
  ~TestBrowserClient() override {}

  void UpdateDevToolsBackgroundServiceExpiration(
      BrowserContext* browser_context,
      int service,
      base::Time expiration_time) override {
    exp_dict_[service] = expiration_time;
  }

  base::flat_map<int, base::Time> GetDevToolsBackgroundServiceExpirations(
      BrowserContext* browser_context) override {
    return exp_dict_;
  }

 private:
  base::flat_map<int, base::Time> exp_dict_;
};

void DidRegisterServiceWorker(int64_t* out_service_worker_registration_id,
                              base::OnceClosure quit_closure,
                              blink::ServiceWorkerStatusCode status,
                              const std::string& status_message,
                              int64_t service_worker_registration_id) {
  DCHECK(out_service_worker_registration_id);
  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) << status_message;

  *out_service_worker_registration_id = service_worker_registration_id;

  std::move(quit_closure).Run();
}

void DidFindServiceWorkerRegistration(
    scoped_refptr<ServiceWorkerRegistration>* out_service_worker_registration,
    base::OnceClosure quit_closure,
    blink::ServiceWorkerStatusCode status,
    scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
  DCHECK(out_service_worker_registration);
  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status)
      << blink::ServiceWorkerStatusToString(status);

  *out_service_worker_registration = service_worker_registration;

  std::move(quit_closure).Run();
}

void DidGetLoggedBackgroundServiceEvents(
    base::OnceClosure quit_closure,
    std::vector<devtools::proto::BackgroundServiceEvent>* out_feature_states,
    std::vector<devtools::proto::BackgroundServiceEvent> feature_states) {
  *out_feature_states = std::move(feature_states);
  std::move(quit_closure).Run();
}

}  // namespace

class DevToolsBackgroundServicesContextTest
    : public ::testing::Test,
      DevToolsBackgroundServicesContextImpl::EventObserver {
 public:
  DevToolsBackgroundServicesContextTest()
      : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP),
        embedded_worker_test_helper_(base::FilePath() /* in memory */) {}

  DevToolsBackgroundServicesContextTest(
      const DevToolsBackgroundServicesContextTest&) = delete;
  DevToolsBackgroundServicesContextTest& operator=(
      const DevToolsBackgroundServicesContextTest&) = delete;

  ~DevToolsBackgroundServicesContextTest() override = default;

  void SetUp() override {
    // Register Service Worker.
    service_worker_registration_id_ = RegisterServiceWorker();
    ASSERT_NE(service_worker_registration_id_,
              blink::mojom::kInvalidServiceWorkerRegistrationId);

    browser_client_ = std::make_unique<TestBrowserClient>();
    scoped_content_browser_client_setting_ =
        std::make_unique<ScopedContentBrowserClientSetting>(
            browser_client_.get());

    SimulateBrowserRestart();
  }

  void TearDown() override { context_->RemoveObserver(this); }

  mojo::Remote<storage::mojom::ServiceWorkerStorageControl>& storage_control() {
    return embedded_worker_test_helper_.context()
        ->registry()
        .GetRemoteStorageControl();
  }

 protected:
  MOCK_METHOD1(OnEventReceived,
               void(const devtools::proto::BackgroundServiceEvent& event));
  MOCK_METHOD2(OnRecordingStateChanged,
               void(bool shoul_record,
                    devtools::proto::BackgroundService service));

  void SimulateBrowserRestart() {
    if (context_)
      context_->RemoveObserver(this);
    // Create |context_|.
    context_ = std::make_unique<DevToolsBackgroundServicesContextImpl>(
        &browser_context_, embedded_worker_test_helper_.context_wrapper());
    context_->AddObserver(this);
    ASSERT_TRUE(context_);
  }

  void SimulateOneWeekPassing() {
    base::Time one_week_ago = base::Time::Now() - base::Days(7);
    context_->expiration_times_
        [devtools::proto::BackgroundService::BACKGROUND_FETCH] = one_week_ago;
  }

  bool IsRecording() {
    return context_->IsRecording(
        devtools::proto::BackgroundService::BACKGROUND_FETCH);
  }

  base::Time GetExpirationTime() {
    return context_->expiration_times_
        [devtools::proto::BackgroundService::BACKGROUND_FETCH];
  }

  std::vector<devtools::proto::BackgroundServiceEvent>
  GetLoggedBackgroundServiceEvents() {
    std::vector<devtools::proto::BackgroundServiceEvent> feature_states;

    base::RunLoop run_loop;
    context_->GetLoggedBackgroundServiceEvents(
        devtools::proto::BackgroundService::BACKGROUND_FETCH,
        base::BindOnce(&DidGetLoggedBackgroundServiceEvents,
                       run_loop.QuitClosure(), &feature_states));
    run_loop.Run();

    return feature_states;
  }

  void LogTestBackgroundServiceEvent(const std::string& log_message) {
    context_->LogBackgroundServiceEvent(
        service_worker_registration_id_,
        blink::StorageKey::CreateFirstParty(origin_),
        DevToolsBackgroundService::kBackgroundFetch, kEventName, kInstanceId,
        {{"key", log_message}});
  }

  void StartRecording() {
    EXPECT_CALL(
        *this, OnRecordingStateChanged(
                   true, devtools::proto::BackgroundService::BACKGROUND_FETCH));
    context_->StartRecording(
        devtools::proto::BackgroundService::BACKGROUND_FETCH);

    // Wait for the messages to propagate to the browser client.
    task_environment_.RunUntilIdle();
  }

  void StopRecording() {
    EXPECT_CALL(
        *this,
        OnRecordingStateChanged(
            false, devtools::proto::BackgroundService::BACKGROUND_FETCH));
    context_->StopRecording(
        devtools::proto::BackgroundService::BACKGROUND_FETCH);

    // Wait for the messages to propagate to the browser client.
    task_environment_.RunUntilIdle();
  }

  void ClearLoggedBackgroundServiceEvents() {
    context_->ClearLoggedBackgroundServiceEvents(
        devtools::proto::BackgroundService::BACKGROUND_FETCH);
  }

  BrowserTaskEnvironment task_environment_;  // Must be first member.
  url::Origin origin_ = url::Origin::Create(GURL("https://example.com"));
  int64_t service_worker_registration_id_ =
      blink::mojom::kInvalidServiceWorkerRegistrationId;

 private:
  int64_t RegisterServiceWorker() {
    GURL script_url(origin_.GetURL().spec() + "sw.js");
    const blink::StorageKey key = blink::StorageKey::CreateFirstParty(origin_);
    int64_t service_worker_registration_id =
        blink::mojom::kInvalidServiceWorkerRegistrationId;

    {
      blink::mojom::ServiceWorkerRegistrationOptions options;
      options.scope = origin_.GetURL();
      base::RunLoop run_loop;
      embedded_worker_test_helper_.context()->RegisterServiceWorker(
          script_url, key, options,
          blink::mojom::FetchClientSettingsObject::New(),
          base::BindOnce(&DidRegisterServiceWorker,
                         &service_worker_registration_id,
                         run_loop.QuitClosure()),
          /*requesting_frame_id=*/GlobalRenderFrameHostId(),
          PolicyContainerPolicies());

      run_loop.Run();
    }

    if (service_worker_registration_id ==
        blink::mojom::kInvalidServiceWorkerRegistrationId) {
      ADD_FAILURE() << "Could not obtain a valid Service Worker registration";
      return blink::mojom::kInvalidServiceWorkerRegistrationId;
    }

    {
      base::RunLoop run_loop;
      embedded_worker_test_helper_.context()->registry().FindRegistrationForId(
          service_worker_registration_id, key,
          base::BindOnce(&DidFindServiceWorkerRegistration,
                         &service_worker_registration_,
                         run_loop.QuitClosure()));
      run_loop.Run();
    }

    // Wait for the worker to be activated.
    base::RunLoop().RunUntilIdle();

    if (!service_worker_registration_) {
      ADD_FAILURE() << "Could not find the new Service Worker registration.";
      return blink::mojom::kInvalidServiceWorkerRegistrationId;
    }

    return service_worker_registration_id;
  }

  EmbeddedWorkerTestHelper embedded_worker_test_helper_;
  TestBrowserContext browser_context_;
  std::unique_ptr<DevToolsBackgroundServicesContextImpl> context_;
  scoped_refptr<ServiceWorkerRegistration> service_worker_registration_;
  std::unique_ptr<ContentBrowserClient> browser_client_;
  std::unique_ptr<ScopedContentBrowserClientSetting>
      scoped_content_browser_client_setting_;
};

// Flaky on Fuchsia.
// TODO(crbug.com/40936408): Reenable test on Fuchsia.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_NothingStoredWithRecordingModeOff \
  DISABLED_NothingStoredWithRecordingModeOff
#else
#define MAYBE_NothingStoredWithRecordingModeOff \
  NothingStoredWithRecordingModeOff
#endif
TEST_F(DevToolsBackgroundServicesContextTest,
       MAYBE_NothingStoredWithRecordingModeOff) {
  // Initially there are no entries.
  EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty());

  // "Log" some events and wait for them to finish.
  LogTestBackgroundServiceEvent("f1");
  LogTestBackgroundServiceEvent("f2");

  // There should still be nothing since recording mode is off.
  EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty());
}

TEST_F(DevToolsBackgroundServicesContextTest, GetLoggedEvents) {
  StartRecording();

  // "Log" some events and wait for them to finish.
  LogTestBackgroundServiceEvent("f1");
  LogTestBackgroundServiceEvent("f2");
  storage_control().FlushForTesting();

  // Check the values.
  auto feature_events = GetLoggedBackgroundServiceEvents();
  ASSERT_EQ(feature_events.size(), 2u);

  for (const auto& feature_event : feature_events) {
    EXPECT_EQ(feature_event.background_service(),
              devtools::proto::BackgroundService::BACKGROUND_FETCH);
    EXPECT_EQ(feature_event.origin(), origin_.GetURL().spec());
    EXPECT_EQ(feature_event.service_worker_registration_id(),
              service_worker_registration_id_);
    EXPECT_EQ(feature_event.event_name(), kEventName);
    EXPECT_EQ(feature_event.instance_id(), kInstanceId);
    ASSERT_EQ(feature_event.event_metadata().size(), 1u);
  }

  EXPECT_EQ(feature_events[0].event_metadata().at("key"), "f1");
  EXPECT_EQ(feature_events[1].event_metadata().at("key"), "f2");

  EXPECT_LE(feature_events[0].timestamp(), feature_events[1].timestamp());
}

TEST_F(DevToolsBackgroundServicesContextTest, StopRecording) {
  StartRecording();
  // Initially there are no entries.
  EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty());

  // "Log" some events and wait for them to finish.
  LogTestBackgroundServiceEvent("f1");
  StopRecording();
  LogTestBackgroundServiceEvent("f2");

  // Check the values.
  ASSERT_EQ(GetLoggedBackgroundServiceEvents().size(), 1u);
}

TEST_F(DevToolsBackgroundServicesContextTest, DelegateExpirationTimes) {
  // Initially expiration time is null.
  EXPECT_TRUE(GetExpirationTime().is_null());
  EXPECT_FALSE(IsRecording());

  // Toggle Recording mode, and now this should be non-null.
  StartRecording();
  EXPECT_FALSE(GetExpirationTime().is_null());
  EXPECT_TRUE(IsRecording());

  // The value should still be there on browser restarts.
  SimulateBrowserRestart();
  EXPECT_FALSE(GetExpirationTime().is_null());
  EXPECT_TRUE(IsRecording());

  // Stopping Recording mode should clear the value.
  StopRecording();
  EXPECT_TRUE(GetExpirationTime().is_null());
  EXPECT_FALSE(IsRecording());
  SimulateBrowserRestart();
  EXPECT_TRUE(GetExpirationTime().is_null());
  EXPECT_FALSE(IsRecording());
}

TEST_F(DevToolsBackgroundServicesContextTest, RecordingExpiration) {
  // Initially expiration time is null.
  EXPECT_FALSE(IsRecording());

  // Toggle Recording mode, and now this should be non-null.
  StartRecording();
  EXPECT_TRUE(IsRecording());

  SimulateOneWeekPassing();
  EXPECT_FALSE(GetExpirationTime().is_null());

  // Recording should be true, with an expired value.
  EXPECT_TRUE(IsRecording());

  // Logging should not happen.
  EXPECT_CALL(*this, OnEventReceived(_)).Times(0);
  // Observers should be informed when recording stops.
  EXPECT_CALL(*this,
              OnRecordingStateChanged(
                  false, devtools::proto::BackgroundService::BACKGROUND_FETCH));
  LogTestBackgroundServiceEvent("f1");

  task_environment_.RunUntilIdle();

  // The expiration time entry should be cleared.
  EXPECT_TRUE(GetExpirationTime().is_null());
  EXPECT_FALSE(IsRecording());
}

TEST_F(DevToolsBackgroundServicesContextTest, ClearLoggedEvents) {
  StartRecording();

  // "Log" some events and wait for them to finish.
  LogTestBackgroundServiceEvent("f1");
  LogTestBackgroundServiceEvent("f2");
  storage_control().FlushForTesting();

  // Check the values.
  auto feature_events = GetLoggedBackgroundServiceEvents();
  ASSERT_EQ(feature_events.size(), 2u);

  ClearLoggedBackgroundServiceEvents();
  storage_control().FlushForTesting();

  // Should be empty now.
  feature_events = GetLoggedBackgroundServiceEvents();
  EXPECT_TRUE(feature_events.empty());
}

TEST_F(DevToolsBackgroundServicesContextTest, EventObserverCalled) {
  {
    EXPECT_CALL(*this, OnEventReceived(_)).Times(0);
    LogTestBackgroundServiceEvent("f1");
    task_environment_.RunUntilIdle();
  }

  StartRecording();

  {
    EXPECT_CALL(*this, OnEventReceived(_));
    LogTestBackgroundServiceEvent("f2");
    task_environment_.RunUntilIdle();
  }
}

}  // namespace content