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

#include <tuple>

#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/with_feature_override.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/service_worker/service_worker_task_queue.h"
#include "extensions/browser/service_worker/service_worker_test_utils.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/test/extension_background_page_waiter.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {
// TODO(crbug.com/40909770): Create test cases where we test "failures" like
// events not acking.

using ContextType = extensions::browser_test_util::ContextType;
using EventMetricsBrowserTest = ExtensionBrowserTest;
using service_worker_test_utils::TestServiceWorkerTaskQueueObserver;

using service_worker_test_utils::TestServiceWorkerContextObserver;

// Tests that the only the dispatch time histogram provided to the test is
// emitted with a sane value, and that other provided metrics are not emitted.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest, DispatchMetricTest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  struct {
    const std::string event_metric_emitted;
    ContextType context_type;
    const std::vector<std::string> event_metrics_not_emitted;
  } test_cases[] = {
      // DispatchToAckTime
      {"Extensions.Events.DispatchToAckTime.ExtensionEventPage3",
       ContextType::kFromManifest,  // event page
       {"Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
        "Extensions.Events.DispatchToAckTime."
        "ExtensionPersistentBackgroundPage"}},
      {"Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
       ContextType::kServiceWorker,
       {"Extensions.Events.DispatchToAckTime.ExtensionEventPage3",
        "Extensions.Events.DispatchToAckTime."
        "ExtensionPersistentBackgroundPage"}},
      // TODO(crbug.com/40909770): Add `event_metrics_not_emitted` when other
      // versions are created.
      // DispatchToAckLongTime
      {"Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2",
       ContextType::kServiceWorker,
       {}},
      // DidDispatchToAckSucceed
      {"Extensions.Events.DidDispatchToAckSucceed.ExtensionPage",
       ContextType::kFromManifest,  // event page
       {"Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3"}},
      {"Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
       ContextType::kServiceWorker,
       {"Extensions.Events.DidDispatchToAckSucceed.ExtensionPage"}},
  };

  for (const auto& test_case : test_cases) {
    ExtensionTestMessageListener extension_oninstall_listener_fired(
        "installed listener fired");
    // Load the extension for the particular context type. The manifest
    // file is for a legacy event page-based extension. LoadExtension will
    // modify the extension for the kServiceWorker case.
    scoped_refptr<const Extension> extension = LoadExtension(
        test_data_dir_.AppendASCII("events/metrics/web_navigation"),
        {.context_type = test_case.context_type});
    ASSERT_TRUE(extension);
    // This ensures that we wait until the the browser receives the ack from the
    // renderer. This prevents unexpected histogram emits later.
    ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

    base::HistogramTester histogram_tester;
    ExtensionTestMessageListener test_event_listener_fired("listener fired");
    // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
    // the extension listener.
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(),
        embedded_test_server()->GetURL("example.com", "/simple.html")));
    ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

    // Call to webNavigation.onCompleted expected.
    histogram_tester.ExpectTotalCount(test_case.event_metric_emitted,
                                      /*expected_count=*/1);

    // Verify that the recorded values are sane -- that is, that they are less
    // than the maximum bucket.
    histogram_tester.ExpectBucketCount(
        test_case.event_metric_emitted,
        /*sample=*/base::Minutes(5).InMicroseconds(), /*expected_count=*/0);
    // Verify other extension context types are not logged.
    for (const auto& event_metric_not_emitted :
         test_case.event_metrics_not_emitted) {
      SCOPED_TRACE(event_metric_not_emitted);
      histogram_tester.ExpectTotalCount(event_metric_not_emitted,
                                        /*expected_count=*/0);
    }

    // Prevent extensions persisting across test cases and emitting extra
    // metrics for events.
    UninstallExtension(extension->id());
  }
}

// Tests that only the dispatch time histogram for a persistent background page
// extension is emitted with a sane value, and that the same metric for other
// background context types are not emitted.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       PersistentBackgroundDispatchMetricTest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // Load the extension for a persistent background page
  scoped_refptr<const Extension> extension = LoadExtension(
      test_data_dir_.AppendASCII("events/metrics/persistent_background"));
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionPersistentBackgroundPage",
      /*expected_count=*/1);

  // Verify that the recorded values are sane -- that is, that they are less
  // than the maximum bucket.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DispatchToAckTime.ExtensionPersistentBackgroundPage",
      /*sample=*/base::Minutes(5).InMicroseconds(), /*expected_count=*/0);
  // Verify other extension background context types are not logged.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
      /*expected_count=*/0);
}

// Tests that only the dispatch time histogram for a persistent background page
// extension is emitted with a sane value, and that the same metric for other
// background context types are not emitted.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       PersistentBackgroundStaleEventsMetricTest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // Load the extension for a persistent background page
  scoped_refptr<const Extension> extension = LoadExtension(
      test_data_dir_.AppendASCII("events/metrics/persistent_background"));
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionPersistentPage",
      /*expected_count=*/1);
  // Verify that the value is `true` since the event wasn't delayed in acking.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionPersistentPage",
      /*sample=*/true, /*expected_count=*/1);

  // Verify other extension background context types are not logged.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionPage",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
      /*expected_count=*/0);
}

// Tests that for every event received there is a corresponding emit of starting
// and finishing status of the service worker external request.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest, ExternalRequestMetrics) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // Load the extension for the particular context type. The manifest
  // file is for a legacy event page-based extension. LoadExtension will
  // modify the extension for the kServiceWorker case.
  base::HistogramTester histogram_tester_oninstalled;
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("events/metrics/web_navigation"),
                    {.context_type = ContextType::kServiceWorker});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  // Call to runtime.onInstalled expected.
  histogram_tester_oninstalled.ExpectTotalCount(
      "Extensions.ServiceWorkerBackground.StartingExternalRequest_Result",
      /*expected_count=*/1);
  histogram_tester_oninstalled.ExpectTotalCount(
      "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result",
      /*expected_count=*/1);
  histogram_tester_oninstalled.ExpectTotalCount(
      "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result_"
      "PostReturn",
      /*expected_count=*/1);
}

// Tests that an active event page will emit the proper dispatch time metric.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       EventPageDispatchToAckTimeActive) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Extend background page expiration time so that the event page will be
  // active for the test.
  ProcessManager::SetEventPageIdleTimeForTesting(60000);
  ProcessManager::SetEventPageSuspendingTimeForTesting(60000);

  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  scoped_refptr<const Extension> extension =
      LoadExtension(test_data_dir_.AppendASCII("events/metrics/web_navigation"),
                    {.context_type = ContextType::kEventPage});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  ExtensionBackgroundPageWaiter(profile(), *extension).WaitForBackgroundOpen();
  ProcessManager* process_manager = ProcessManager::Get(profile());
  ASSERT_TRUE(process_manager->GetBackgroundHostForExtension(extension->id()));

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Active",
      /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Inactive",
      /*expected_count=*/0);
  // Verify that the recorded values are sane -- that is, that they are less
  // than the maximum bucket.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Active",
      /*sample=*/base::Minutes(5).InMicroseconds(),
      /*expected_count=*/0);
}

// Tests that an inactive event page will emit the proper dispatch time metric.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       EventPageDispatchToAckTimeInactive) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Minimize background page expiration time so that the event page will
  // suspend/idle quickly for the test.
  ProcessManager::SetEventPageIdleTimeForTesting(1);
  ProcessManager::SetEventPageSuspendingTimeForTesting(1);

  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  scoped_refptr<const Extension> extension =
      LoadExtension(test_data_dir_.AppendASCII("events/metrics/web_navigation"),
                    {.context_type = ContextType::kEventPage});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  ExtensionBackgroundPageWaiter(profile(), *extension)
      .WaitForBackgroundClosed();
  ProcessManager* process_manager = ProcessManager::Get(profile());
  ASSERT_FALSE(process_manager->GetBackgroundHostForExtension(extension->id()));

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Inactive",
      /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Active",
      /*expected_count=*/0);
  // Verify that the recorded values are sane -- that is, that they are less
  // than the maximum bucket.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3.Inactive",
      /*sample=*/base::Minutes(5).InMicroseconds(),
      /*expected_count=*/0);
}

// Tests that an active service worker will emit the proper dispatch time
// metric.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       ServiceWorkerDispatchToAckTimeActive) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // Load the extension for the particular context type. The manifest
  // file is for a legacy event page-based extension. LoadExtension will
  // modify the extension for the kServiceWorker case.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("events/metrics/web_navigation"),
                    {.context_type = ContextType::kServiceWorker});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
  ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
      // The first SW version ID is always 0.
      GetServiceWorkerContext(), /*service_worker_version_id=*/0));

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Active3",
      /*expected_count=*/1);
  // Verify that the recorded values are sane -- that is, that they are less
  // than the maximum bucket.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Active3",
      /*sample=*/base::Minutes(5).InMicroseconds(), /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Inactive3",
      /*expected_count=*/0);
}

// Tests that an inactive service worker will emit the proper dispatch time
// metric.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       ServiceWorkerDispatchToAckTimeInactive) {
  ASSERT_TRUE(embedded_test_server()->Start());
  constexpr char kTestExtensionId[] = "iegclhlplifhodhkoafiokenjoapiobj";
  // Stop the service worker to make it inactive.
  TestServiceWorkerContextObserver test_worker_start_stop_observer(
      profile(), kTestExtensionId);
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // We need to load an extension where we know the extensions ID so that we
  // can correctly observe when the worker starts and stops.
  const Extension* extension = LoadExtension(
      test_data_dir_.AppendASCII("events/reliability/service_worker"),
      {.wait_for_registration_stored = true});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
  const int64_t test_worker_version_id =
      test_worker_start_stop_observer.WaitForWorkerStarted();

  browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(),
                                                             kTestExtensionId);
  test_worker_start_stop_observer.WaitForWorkerStopped();
  ASSERT_TRUE(content::CheckServiceWorkerIsStopped(GetServiceWorkerContext(),
                                                   test_worker_version_id));

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Call to webNavigation.onCompleted expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Inactive3",
      /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Active3",
      /*expected_count=*/0);
  // Verify that the recorded values are sane -- that is, that they are less
  // than the maximum bucket.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2.Inactive3",
      /*sample=*/base::Minutes(5).InMicroseconds(),
      /*expected_count=*/0);
}

// Tests that when an event is "late" in being acked (not acked within a certain
// time) that we emit failure metrics for it.
// TODO(crbug.com/338378835): test is flaky across platforms.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       DISABLED_ServiceWorkerLateEventAckMetricTest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  static constexpr char kManifest[] =
      R"({
            "name": "onBeforeNavigate never acks",
            "manifest_version": 3,
            "version": "0.1",
            "background": {
              "service_worker" : "background.js"
            },
            "permissions": ["webNavigation"]
        })";
  // The extensions script listens for runtime.onInstalled (to detect install
  // and worker start completion) and webNavigation.onBeforeNavigate (to request
  // worker start).
  static constexpr char kBackgroundScript[] =
      R"(
            chrome.runtime.onInstalled.addListener((details) => {
                chrome.test.sendMessage('installed listener fired');
            });
            chrome.webNavigation.onBeforeNavigate.addListener((details) => {
              // Loop infinitely to prevent the acknowledgement from the
              // renderer back to browser process.
              while (true) {};
            });
        )";
  auto test_dir = std::make_unique<TestExtensionDir>();
  test_dir->WriteManifest(kManifest);
  test_dir->WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScript);
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  const Extension* extension = LoadExtension(test_dir->UnpackedPath());
  ASSERT_TRUE(extension);
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  // Set the event ack timeout to be very small to avoid waiting awhile.
  EventRouter* event_router = EventRouter::Get(profile());
  ASSERT_TRUE(event_router);
  event_router->SetEventAckMetricTimeLimitForTesting(base::Microseconds(1));

  // Dispatch an event that the renderer will never ack (that the event was
  // executed), and will be considered "late".
  base::HistogramTester histogram_tester;
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));

  {
    // Wait a bit to ensure the late event ack task ran.
    SCOPED_TRACE("Waiting for late acked event task to run.");
    base::RunLoop late_ack_metric_task_waiter;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, late_ack_metric_task_waiter.QuitClosure(),
        base::Microseconds(2));
    late_ack_metric_task_waiter.Run();
  }

  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
      /*expected_count=*/1);
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
      /*sample=*/false, /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.ServiceWorkerDispatchFailed.Event",
      /*expected_count=*/1);
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.ServiceWorkerDispatchFailed.Event",
      /*sample=*/events::WEB_NAVIGATION_ON_BEFORE_NAVIGATE,
      /*expected_count=*/1);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.ServiceWorkerDispatchFailed.StartExternalRequestOk",
      /*expected_count=*/1);
  // TODO(jlulejian): See if a failed
  // ServiceWorkerContext::StartingExternalRequest() can be simulated during the
  // test so we can test
  // Extensions.Events.ServiceWorkerDispatchFailed.StartExternalRequestResult.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.ServiceWorkerDispatchFailed.StartExternalRequestOk",
      /*sample=*/true, /*expected_count=*/1);

  // Verify non-late ack event metrics are not logged.
  histogram_tester.ExpectBucketCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
      /*sample=*/true, /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result",
      /*expected_count=*/0);
}

// Tests that a running service worker will not be unnecessarily started when it
// receives an event while it is already started.
IN_PROC_BROWSER_TEST_F(EventMetricsBrowserTest,
                       ServiceWorkerRedundantStartCountTest) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  // Load the extension for the particular context type. The manifest
  // file is for a legacy event page-based extension. LoadExtension will
  // modify the extension for the kServiceWorker case.
  const Extension* extension =
      LoadExtension(test_data_dir_.AppendASCII("events/metrics/web_navigation"),
                    {.wait_for_registration_stored = true,
                     .context_type = ContextType::kServiceWorker});
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
  ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
      // The first SW version ID is always 0.
      GetServiceWorkerContext(), /*service_worker_version_id=*/0));

  base::HistogramTester histogram_tester;
  TestServiceWorkerTaskQueueObserver ready_observer;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger the webNavigation.onBeforeRequest event to
  // the extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  ASSERT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // We do check if a worker is ready before attempting to
  // start it again.
  histogram_tester.ExpectTotalCount(
      "Extensions.ServiceWorkerBackground."
      "RequestedWorkerStartForStartedWorker3",
      /*expected_count=*/0);
  // Verify that the value is `true` since the worker
  // will be unnecessarily started.
  histogram_tester.ExpectBucketCount(
      "Extensions.ServiceWorkerBackground."
      "RequestedWorkerStartForStartedWorker3",
      /*sample=*/true, /*expected_count=*/0);
}

// Runs webRequest dispatch metrics tests for each background context type,
// with kWebRequestPerContextEventDispatch disabled (legacy) and enabled
// (per-context).
class EventMetricsWebRequestDispatchBrowserTest
    : public ExtensionBrowserTest,
      public testing::WithParamInterface<std::tuple<ContextType, bool>> {
 public:
  EventMetricsWebRequestDispatchBrowserTest() {
    feature_list_.InitWithFeatureState(
        extensions_features::kWebRequestPerContextEventDispatch,
        IsPerContextDispatch());
  }

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

  // Names a test instance after its dispatch mode. The instantiation prefix
  // names the context type.
  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    const auto& [context_type, per_context_dispatch] = info.param;
    return per_context_dispatch ? "PerContextDispatch" : "LegacyDispatch";
  }

 protected:
  ContextType GetContextType() const { return std::get<0>(GetParam()); }
  bool IsPerContextDispatch() const { return std::get<1>(GetParam()); }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests that webRequest events to active listeners record dispatch-to-ack
// metrics only under per-context dispatch.
IN_PROC_BROWSER_TEST_P(EventMetricsWebRequestDispatchBrowserTest,
                       DispatchToAckMetricsForActiveListeners) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Load either a persistent background page or a service worker extension
  // with webRequest permission.
  static constexpr char kManifestPersistentBackgroundScript[] =
      R"({"scripts": ["background.js"], "persistent": true})";
  static constexpr char kManifestServiceWorkerBackgroundScript[] =
      R"({"service_worker": "background.js"})";
  static constexpr char kManifestPersistentBackgroundPermissions[] =
      R"("permissions": ["webRequest", "http://example.com/*"])";
  static constexpr char kManifestServiceWorkerPermissions[] =
      R"(
          "host_permissions": [
            "http://example.com/*"
          ],
          "permissions": ["webRequest"]
      )";

  static constexpr char kManifest[] =
      R"({
        "name": "Test Extension",
        "manifest_version": %s,
        "version": "0.1",
        "background": %s,
        %s
      })";
  bool persistent_background_extension =
      GetContextType() == ContextType::kPersistentBackground;
  const char* background_script = persistent_background_extension
                                      ? kManifestPersistentBackgroundScript
                                      : kManifestServiceWorkerBackgroundScript;
  const char* manifest_version = persistent_background_extension ? "2" : "3";
  const char* permissions = persistent_background_extension
                                ? kManifestPersistentBackgroundPermissions
                                : kManifestServiceWorkerPermissions;
  std::string manifest = base::StringPrintf(kManifest, manifest_version,
                                            background_script, permissions);

  // The extensions script listens for runtime.onInstalled and
  // webRequest.onBeforeRequest.
  static constexpr char kScript[] =
      R"({
        chrome.runtime.onInstalled.addListener((details) => {
          // Asynchronously send the message that the listener fired so that the
          // event is considered ack'd in the browser C++ code.
          setTimeout(() => {
            chrome.test.sendMessage('installed listener fired');
          }, 0);
        });

        chrome.webRequest.onBeforeRequest.addListener(
          (details) => {
            setTimeout(() => {
              chrome.test.sendMessage('listener fired');
            }, 0);
          },
          {urls: ['<all_urls>'], types: ['main_frame']},
          []
        );
      })";

  TestExtensionDir test_dir;
  test_dir.WriteManifest(manifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kScript);
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener test_event_listener_fired("listener fired");
  // Navigate somewhere to trigger webRequest.onBeforeRequest event to the
  // extension listener.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("example.com", "/simple.html")));
  EXPECT_TRUE(test_event_listener_fired.WaitUntilSatisfied());

  // Legacy DispatchEventToSender() excludes these events from dispatch
  // histograms, while per-context DispatchEventToProcess() records them.
  const int service_worker_count =
      !persistent_background_extension && IsPerContextDispatch() ? 1 : 0;
  const int persistent_page_count =
      persistent_background_extension && IsPerContextDispatch() ? 1 : 0;
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
      service_worker_count);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2",
      service_worker_count);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker3",
      service_worker_count);
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionPersistentBackgroundPage",
      persistent_page_count);
  // The event page variant is never emitted for these extensions.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionPage",
      /*expected_count=*/0);
  // Not gated on the dispatch flow: the persistent background page acks the
  // event in both flows.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DidDispatchToAckSucceed.ExtensionPersistentPage",
      persistent_background_extension ? 1 : 0);

  // We do always log starting/finishing an external request.
  if (!persistent_background_extension) {  // service worker
    histogram_tester.ExpectTotalCount(
        "Extensions.ServiceWorkerBackground.StartingExternalRequest_Result",
        /*expected_count=*/1);
    histogram_tester.ExpectTotalCount(
        "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result",
        /*expected_count=*/1);
    histogram_tester.ExpectTotalCount(
        "Extensions.ServiceWorkerBackground.FinishedExternalRequest_Result_"
        "PostReturn",
        /*expected_count=*/1);
  }
}

INSTANTIATE_TEST_SUITE_P(
    PersistentBackground,
    EventMetricsWebRequestDispatchBrowserTest,
    ::testing::Combine(::testing::Values(ContextType::kPersistentBackground),
                       /*per_context_dispatch=*/::testing::Bool()),
    EventMetricsWebRequestDispatchBrowserTest::DescribeParams);
INSTANTIATE_TEST_SUITE_P(
    ServiceWorker,
    EventMetricsWebRequestDispatchBrowserTest,
    ::testing::Combine(::testing::Values(ContextType::kServiceWorker),
                       /*per_context_dispatch=*/::testing::Bool()),
    EventMetricsWebRequestDispatchBrowserTest::DescribeParams);

class LazyBackgroundEventMetricsApiTest : public ExtensionApiTest {
 public:
  LazyBackgroundEventMetricsApiTest() = default;

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

  void SetUpOnMainThread() override {
    ExtensionApiTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(StartEmbeddedTestServer());
  }
};

// Tests that if there is a listener in the extension renderer process, but that
// listener is not in the lazy background page script, then do not emit
// background context event dispatching histograms.
IN_PROC_BROWSER_TEST_F(
    LazyBackgroundEventMetricsApiTest,
    ContextsOutsideLazyBackgroundDoNotEmitBackgroundContextMetrics) {
  // Load an extension with a page script that runs in the extension renderer
  // process, and has the only chrome.storage.onChanged listener.
  static constexpr char kManifest[] =
      R"({
           "name": "Event page",
           "version": "0.1",
           "manifest_version": 2,
           "background": {
             "scripts": ["background.js"],
             "persistent": false
            },
           "permissions": ["storage"]
         })";
  TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);
  constexpr char kPageHtml[] = R"(<script src="page.js"></script>)";
  test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPageHtml);
  constexpr char kPageScriptJs[] =
      R"(
       chrome.storage.onChanged.addListener((details) => {
         // Asynchronously send the message that the listener fired so that the
         // event is considered ack'd in the browser C++ code.
         setTimeout(() => {
           chrome.test.sendMessage('listener fired');
         }, 0);
       });

       chrome.test.sendMessage('page script loaded');
      )";
  test_dir.WriteFile(FILE_PATH_LITERAL("page.js"), kPageScriptJs);
  constexpr char kBackgroundJs[] =
      R"(
      chrome.runtime.onInstalled.addListener((details) => {
        // Asynchronously send the message that the listener fired so that the
        // event is considered ack'd in the browser C++ code.
        setTimeout(() => {
          chrome.test.sendMessage('installed listener fired');
        }, 0);
      });
    )";
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
  ExtensionTestMessageListener extension_oninstall_listener_fired(
      "installed listener fired");
  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
  ASSERT_TRUE(extension);
  // This ensures that we wait until the the browser receives the ack from the
  // renderer. This prevents unexpected histogram emits later.
  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());

  ExtensionTestMessageListener page_script_loaded("page script loaded");
  // Navigate to page.html to get the content_script to load.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), extension->GetResourceURL("page.html")));
  ASSERT_TRUE(content::WaitForLoadStop(GetActiveWebContents()));
  ASSERT_TRUE(page_script_loaded.WaitUntilSatisfied());

  // Set storage value which should fire chrome.storage.onChanged listener in
  // the page.
  base::HistogramTester histogram_tester;
  ExtensionTestMessageListener page_script_event_listener_fired(
      "listener fired");
  static constexpr char kScript[] =
      R"(chrome.storage.local.set({"key" : "value"});)";
  BackgroundScriptExecutor::ExecuteScriptAsync(profile(), extension->id(),
                                               kScript);

  // Confirm that the listener in the page script was fired, but that we do not
  // emit a histogram for it.
  EXPECT_TRUE(page_script_event_listener_fired.WaitUntilSatisfied());
  // Call to storage.onChanged expected.
  histogram_tester.ExpectTotalCount(
      "Extensions.Events.DispatchToAckTime.ExtensionEventPage3",
      /*expected_count=*/0);
}

}  // namespace

}  // namespace extensions
