// 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 "base/test/metrics/histogram_tester.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "extensions/browser/api/messaging/message_service.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_frame_host.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/message_tracker.h"
#include "extensions/browser/service_worker/service_worker_test_utils.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/mojom/frame.mojom-test-utils.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/dns/mock_host_resolver.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
#endif

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace {

base::FilePath WriteExtensionWithMessagePortToDir(TestExtensionDir* test_dir) {
  test_dir->WriteManifest(R"(
      {
        "name": "Content script disconnect on worker stop test",
        "description": "Tests worker shutdown behavior for messaging",
        "version": "0.1",
        "manifest_version": 2,
        "background": {"scripts": ["background.js"]},
        "content_scripts": [{
          "matches": ["*://example.com:*/*"],
          "js": ["content_script.js"]
        }]
      })");
  test_dir->WriteFile(FILE_PATH_LITERAL("background.js"), R"(
      chrome.runtime.onConnect.addListener(port => {
        console.log('background: runtime.onConnect');
        port.onMessage.addListener(msg => {
          if (msg == 'foo') {
            chrome.test.notifyPass();
          } else
            chrome.test.notifyFail('FAILED');
        });
      });)");
  test_dir->WriteFile(FILE_PATH_LITERAL("content_script.js"), R"(
    var port = chrome.runtime.connect({name:"bar"});
    port.postMessage('foo');)");
  return test_dir->UnpackedPath();
}

base::FilePath WriteServiceWorkerExtensionToDir(TestExtensionDir* test_dir) {
  test_dir->WriteManifest(R"(
      {
        "name": "Test Extension",
        "manifest_version": 2,
        "version": "1.0",
        "background": {"service_worker": "service_worker_background.js"}
      })");
  test_dir->WriteFile(FILE_PATH_LITERAL("service_worker_background.js"),
                      R"(chrome.test.sendMessage('worker_running');)");
  return test_dir->UnpackedPath();
}

}  // namespace

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

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

  ~ServiceWorkerMessagingTest() override = default;

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

 protected:
  void StopServiceWorker(const Extension& extension) {
    // TODO(lazyboy): Ideally we'd want to test worker shutdown on idle, do that
    // once //content API allows to override test timeouts for Service Workers.
    browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(),
                                                               extension.id());
  }

  content::WebContents* AddTab(const GURL& url) {
    EXPECT_TRUE(NavigateToURLInNewTab(url)) << url;
    return GetActiveWebContents();
  }

#if BUILDFLAG(ENABLE_EXTENSIONS)
  extensions::ScopedTestNativeMessagingHost test_host_;
#endif
};

class ServiceWorkerMessagingTestWithActivityLog
    : public ServiceWorkerMessagingTest {
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kEnableExtensionActivityLogging);
    ServiceWorkerMessagingTest::SetUpCommandLine(command_line);
  }
};

// Tests one-way message from content script to SW extension using
// chrome.runtime.sendMessage.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, TabToWorkerOneWay) {
  ExtensionTestMessageListener worker_listener("WORKER_RUNNING");
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/send_message_tab_to_worker_one_way"));
  ASSERT_TRUE(extension);
  EXPECT_TRUE(worker_listener.WaitUntilSatisfied());

  ExtensionTestMessageListener test_listener("WORKER_RECEIVED_MESSAGE");
  test_listener.set_failure_message("FAILURE");

  {
    ASSERT_TRUE(StartEmbeddedTestServer());
    const GURL url =
        embedded_test_server()->GetURL("/extensions/test_file.html");
    content::WebContents* new_web_contents = AddTab(url);
    EXPECT_TRUE(new_web_contents);
  }

  EXPECT_TRUE(test_listener.WaitUntilSatisfied());
}

// Tests chrome.runtime.sendMessage from content script to SW extension.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, TabToWorker) {
  ExtensionTestMessageListener worker_listener("WORKER_RUNNING");
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/send_message_tab_to_worker"));
  ASSERT_TRUE(extension);
  EXPECT_TRUE(worker_listener.WaitUntilSatisfied());

  ExtensionTestMessageListener reply_listener("CONTENT_SCRIPT_RECEIVED_REPLY");
  reply_listener.set_failure_message("FAILURE");

  {
    ASSERT_TRUE(StartEmbeddedTestServer());
    const GURL url =
        embedded_test_server()->GetURL("/extensions/test_file.html");
    content::WebContents* new_web_contents = AddTab(url);
    EXPECT_TRUE(new_web_contents);
  }

  EXPECT_TRUE(reply_listener.WaitUntilSatisfied());
}

// Tests that a message port disconnects if the extension SW is forcefully
// stopped.
// Regression test for https://crbug.com/40663477.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       TabToWorker_StopWorkerDisconnects) {
  ASSERT_TRUE(StartEmbeddedTestServer());

  TestExtensionDir test_dir;
  test_dir.WriteManifest(
      R"({
           "name": "Content script disconnect on worker stop test",
           "description": "Tests worker shutdown behavior for messaging",
           "version": "0.1",
           "manifest_version": 2,
           "background": {"service_worker": "service_worker_background.js"},
           "content_scripts": [{
             "matches": ["*://example.com:*/*"],
             "js": ["content_script.js"]
           }]
         })");
  test_dir.WriteFile(FILE_PATH_LITERAL("service_worker_background.js"),
                     R"(chrome.runtime.onConnect.addListener((port) => {
           self.port = port;
           console.log('background: runtime.onConnect');
           chrome.test.assertNoLastError();
           port.postMessage('connected');
         });
         chrome.test.notifyPass();
      )");
  test_dir.WriteFile(FILE_PATH_LITERAL("content_script.js"),
                     R"(var port = chrome.runtime.connect({name:"foo"});
         port.onMessage.addListener((msg) => {
           if (msg === 'connected') {
             // Wait for the worker to ack the connection.
             chrome.test.notifyPass();
           }
         });
         port.onDisconnect.addListener(() => {
           console.log('content script: port.onDisconnect');
           chrome.test.assertNoLastError();
           chrome.test.notifyPass();
         });
      )");
  ResultCatcher catcher;
  const Extension* extension =
      LoadExtension(test_dir.UnpackedPath(),
                    // Wait for the registration to be stored so that it's
                    // persistent before the worker is stopped later.
                    {.wait_for_registration_stored = true});
  ASSERT_TRUE(extension);

  // Wait for the extension to register runtime.onConnect listener.
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();

  auto* web_contents = GetActiveWebContents();
  GURL url =
      embedded_test_server()->GetURL("example.com", "/extensions/body1.html");
  ASSERT_TRUE(NavigateToURL(web_contents, url));

  // Wait for the round-trip handshake (connect -> postMessage -> onMessage) to
  // complete. This ensures the port is fully established.
  // See crbug.com/440533605.
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();

  // Stop the service worker, this will disconnect the port.
  StopServiceWorker(*extension);

  // Wait for the port to disconnect in the content script.
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
// Regression test for https://crbug.com/40168341.
// Tests that service worker shutdown closes messaging channel properly.
// TODO(crbug.com/417786914): Support native messaging tests on desktop Android.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       WorkerShutsDownWhileNativeMessagePortIsOpen) {
  // Set up an observer to wait for the registration to be stored before
  // calling StopServiceWorker below.
  service_worker_test_utils::TestServiceWorkerContextObserver observer(
      profile());
  ASSERT_NO_FATAL_FAILURE(test_host_.RegisterTestHost(false));

  ResultCatcher catcher;
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/native_message_after_worker_stop"));
  ASSERT_TRUE(extension);

  observer.WaitForRegistrationStored();
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
  size_t num_channels =
      MessageService::Get(profile())->GetChannelCountForTest();
  StopServiceWorker(*extension);
  // After worker shutdown, expect the channel count to reduce by 1.
  EXPECT_EQ(num_channels - 1,
            MessageService::Get(profile())->GetChannelCountForTest());
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

// Tests chrome.tabs.sendMessage from SW extension to content script.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, WorkerToTab) {
  ASSERT_TRUE(StartEmbeddedTestServer());
  ASSERT_TRUE(
      RunExtensionTest("service_worker/messaging/send_message_worker_to_tab"))
      << message_;
}

// Tests that chrome.tabs.sendMessage from SW extension without specifying
// callback doesn't crash.
//
// Regression test for https://crbug.com/40771696.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       TabsSendMessageWithoutCallback) {
  ASSERT_TRUE(StartEmbeddedTestServer());
  ASSERT_TRUE(RunExtensionTest(
      "service_worker/messaging/tabs_send_message_without_callback"))
      << message_;
}

// Tests port creation (chrome.runtime.connect) from content script to an
// extension SW and disconnecting the port.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       TabToWorker_ConnectAndDisconnect) {
  // Load an extension that will inject content script to |new_web_contents|.
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/connect_and_disconnect"));
  ASSERT_TRUE(extension);

  // Load the tab with content script to open a Port to |extension|.
  // Test concludes when extension gets notified about port being disconnected.
  ResultCatcher catcher;
  {
    ASSERT_TRUE(StartEmbeddedTestServer());
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
  }
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}

// Tests port creation (chrome.runtime.connect) from content script to an
// extension and sending message through the port.
// TODO(lazyboy): Refactor common parts with TabToWorker_ConnectAndDisconnect.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       TabToWorker_ConnectAndPostMessage) {
  // Load an extension that will inject content script to |new_web_contents|.
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/post_message"));
  ASSERT_TRUE(extension);

  // Load the tab with content script to send message to |extension| via port.
  // Test concludes when the content script receives a reply.
  ResultCatcher catcher;
  {
    ASSERT_TRUE(StartEmbeddedTestServer());
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
  }
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}

// Tests chrome.runtime.onMessageExternal between two Service Worker based
// extensions.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, ExternalMessageToWorker) {
  const std::string kTargetExtensionId = "pkplfbidichfdicaijlchgnapepdginl";

  // Load the receiver extension first.
  const Extension* target_extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/send_message_external/target"));
  ASSERT_TRUE(target_extension);
  EXPECT_EQ(kTargetExtensionId, target_extension->id());

  // Then run the test from initiator extension.
  ASSERT_TRUE(RunExtensionTest(
      "service_worker/messaging/send_message_external/initiator"))
      << message_;
}

// Tests chrome.runtime.onConnectExternal between two Service Worker based
// extensions.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, ConnectExternalToWorker) {
  const std::string kTargetExtensionId = "pkplfbidichfdicaijlchgnapepdginl";

  // Load the receiver extension first.
  const Extension* target_extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_external/target"));
  ASSERT_TRUE(target_extension);
  EXPECT_EQ(kTargetExtensionId, target_extension->id());

  // Then run the test from initiator extension.
  ASSERT_TRUE(
      RunExtensionTest("service_worker/messaging/connect_external/initiator"))
      << message_;
}

// Tests that an extension's message port isn't affected by an unrelated
// extension's service worker.
//
// This test opens a message port in an extension (message_port_extension) and
// then loads another extension that is service worker based. This test ensures
// that stopping the service worker based extension doesn't DCHECK in
// message_port_extension's message port.
//
// Regression test for https://crbug.com/40687864.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       UnrelatedPortsArentAffectedByServiceWorker) {
  ASSERT_TRUE(StartEmbeddedTestServer());
  // Step 1/2: Load an extension that creates an ExtensionMessagePort from a
  // content script and connects to its background script.
  TestExtensionDir message_port_extension_dir;
  ResultCatcher content_script_connected_catcher;
  const Extension* message_port_extension = LoadExtension(
      WriteExtensionWithMessagePortToDir(&message_port_extension_dir));
  ASSERT_TRUE(message_port_extension);

  // Load the content script for |message_port_extension|, and wait for the
  // content script to connect to its background's port.
  auto* web_contents = GetActiveWebContents();
  GURL url =
      embedded_test_server()->GetURL("example.com", "/extensions/body1.html");
  ASSERT_TRUE(NavigateToURL(web_contents, url));
  EXPECT_TRUE(content_script_connected_catcher.GetNextResult())
      << content_script_connected_catcher.message();

  // Step 2/2: Load an extension with service worker background, verify that
  // stopping the service worker doesn't cause message port in
  // |message_port_extension| to crash.
  ExtensionTestMessageListener worker_running_listener("worker_running");

  TestExtensionDir worker_extension_dir;
  const Extension* service_worker_extension =
      LoadExtension(WriteServiceWorkerExtensionToDir(&worker_extension_dir),
                    {.wait_for_registration_stored = true});
  const ExtensionId worker_extension_id = service_worker_extension->id();
  ASSERT_TRUE(service_worker_extension);

  // Wait for the extension service worker to settle before moving to next step.
  EXPECT_TRUE(worker_running_listener.WaitUntilSatisfied());

  {
    // Stop the worker, and ensure its completion.
    service_worker_test_utils::UnregisterWorkerObserver
        unregister_worker_observer(ProcessManager::Get(profile()),
                                   worker_extension_id);
    StopServiceWorker(*service_worker_extension);
    unregister_worker_observer.WaitForUnregister();
  }
}

// Tests ActiviyLog from SW based extension.
// Regression test for https://crbug.com/40768552, https://crbug.com/40771031.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTestWithActivityLog, ActivityLog) {
  ASSERT_TRUE(StartEmbeddedTestServer());

  const Extension* friend_extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/connect_and_disconnect"));
  ASSERT_TRUE(friend_extension);
  {
    ResultCatcher catcher;
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
    EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
  }

  // The test passes when /activiy_log/ extension sees activities from
  // |friend_extension|.
  ASSERT_TRUE(RunExtensionTest("service_worker/messaging/activity_log/"));
}

// Tests port creation (chrome.runtime.connect) from content script to an
// extension SW should not, by itself, create an external request that keeps the
// SW alive.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, LongLivedChannelNoMessage) {
  ASSERT_TRUE(StartEmbeddedTestServer());

  service_worker_test_utils::TestServiceWorkerContextObserver observer(
      profile());
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/connect"));
  ASSERT_TRUE(extension);
  const int64_t version_id = observer.WaitForWorkerStarted();

  // Load the tab with content script to open a Port to |extension|.
  ResultCatcher catcher;
  {
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
  }
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();

  // The service worker will be terminated because there is no in-flight
  // external request.
  base::RunLoop().RunUntilIdle();
  content::ServiceWorkerContext* context =
      util::GetServiceWorkerContextForExtensionId(extension->id(), profile());
  EXPECT_FALSE(
      content::TriggerTimeoutAndCheckRunningState(context, version_id));
}

// Tests that one-time messages (chrome.runtime.sendMessage) from content script
// to an extension SW should add in-flight external request.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, OneTimeChannel) {
  ASSERT_TRUE(StartEmbeddedTestServer());

  service_worker_test_utils::TestServiceWorkerContextObserver observer(
      profile());
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/send_message"));
  ASSERT_TRUE(extension);
  const int64_t version_id = observer.WaitForWorkerStarted();

  // Load the tab with content script to open a Port to |extension|.
  ResultCatcher catcher;
  {
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
  }
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();

  // The service worker will not be terminated because there is an in-flight
  // external request. The onMessage listener indicates an asynchronous response
  // but never respond. It keeps the port open. As a result the in-flight
  // request is not finished.
  base::RunLoop().RunUntilIdle();
  content::ServiceWorkerContext* context =
      util::GetServiceWorkerContextForExtensionId(extension->id(), profile());
  EXPECT_TRUE(content::TriggerTimeoutAndCheckRunningState(context, version_id));
}

namespace {
// Helper class to intercept `OpenChannelToExtension` call.
class ExtensionFrameHostHelper
    : public mojom::LocalFrameHostInterceptorForTesting {
 public:
  ExtensionFrameHostHelper(content::WebContents* web_contents,
                           base::OnceClosure open_channel_to_extension_cb)
      : scoped_swap_impl_(
            ExtensionWebContentsObserver::GetForWebContents(web_contents)
                ->extension_frame_host_for_testing()
                ->receivers_for_testing(),
            this),
        open_channel_to_extension_cb_(std::move(open_channel_to_extension_cb)) {
  }

 private:
  mojom::LocalFrameHost* GetForwardingInterface() override {
    return scoped_swap_impl_.old_impl();
  }

  // mojom::LocalFrameHostInterceptorForTesting:
  void OpenChannelToExtension(
      mojom::ExternalConnectionInfoPtr info,
      mojom::ChannelType channel_type,
      const std::string& channel_name,
      const PortId& port_id,
      mojo::PendingAssociatedRemote<mojom::MessagePort> port,
      mojo::PendingAssociatedReceiver<mojom::MessagePortHost> port_host)
      override {
    GetForwardingInterface()->OpenChannelToExtension(
        std::move(info), channel_type, channel_name, port_id, std::move(port),
        std::move(port_host));

    if (open_channel_to_extension_cb_) {
      std::move(open_channel_to_extension_cb_).Run();
    }
  }

  const mojo::test::ScopedSwapImplForTesting<mojom::LocalFrameHost>
      scoped_swap_impl_;
  base::OnceClosure open_channel_to_extension_cb_;
};
}  // namespace

// Tests that removing a MV3 extension with a pending message from incognito
// does not crash. See https://crbug.com/443038597
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest, RemoveWithPending) {
  service_worker_test_utils::TestServiceWorkerContextObserver observer(
      profile());
  auto* extension = LoadExtension(
      test_data_dir_.AppendASCII("service_worker/remove_with_pending"),
      {.allow_in_incognito = true});
  ASSERT_TRUE(extension);
  const int64_t version_id = observer.WaitForWorkerStarted();

  // Simulate idle timeout to terminate the service worker.
  content::ServiceWorkerContext* context =
      util::GetServiceWorkerContextForExtensionId(extension->id(), profile());
  content::SetServiceWorkerIdleDelay(context, version_id, base::Seconds(0));
  observer.WaitForWorkerStopped();

  // Open the test page in an incognito window.
  ExtensionTestMessageListener ready("ready", ReplyBehavior::kWillReply);
  content::WebContents* const contents = PlatformOpenURLOffTheRecord(
      profile(), extension->GetResourceURL("test.html"));
  ASSERT_TRUE(ready.WaitUntilSatisfied());

  base::RunLoop wait_for_open;
  ExtensionFrameHostHelper helper(contents, wait_for_open.QuitClosure());

  // Triggers a `chrome.runtime.sendMessage` while the service worker is not
  // active.
  ready.Reply("go");

  // Wait for `OpenChannelToExtension`.
  wait_for_open.Run();

  // Unload the extension with a pending connection. No crash should happen.
  ASSERT_TRUE(
      MessageService::Get(profile())->HasPendingLazyContextChannelsForExtension(
          extension->id()));

  base::HistogramTester histograms;
  UnloadExtension(extension->id());

  // `UnloadExtension` will call `ServiceWorkerTaskQueue::DeactivateExtension`
  // down the line, which will run pending tasks with null context.
  ASSERT_FALSE(
      MessageService::Get(profile())->HasPendingLazyContextChannelsForExtension(
          extension->id()));

  histograms.ExpectUniqueSample(
      "Extensions.MessagePipeline.OpenChannelStatus.SendMessageChannel",
      /*sample=*/MessageTracker::OpenChannelMessagePipelineResult::kNoReceivers,
      /*expected_bucket_count=*/1);
  histograms.ExpectUniqueSample(
      "Extensions.MessagePipeline.OpenChannelWorkerWakeUpStatus."
      "SendMessageChannel",
      /*sample=*/MessageTracker::OpenChannelMessagePipelineResult::kNoReceivers,
      /*expected_bucket_count=*/1);
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
// Tests post messages through a long-lived channel from content script to an
// extension SW should extend SW lifetime.
// TODO(crbug.com/383366125): Flaky on desktop Android.
IN_PROC_BROWSER_TEST_F(ServiceWorkerMessagingTest,
                       LongLivedChannelPostMessage) {
  ASSERT_TRUE(StartEmbeddedTestServer());

  service_worker_test_utils::TestServiceWorkerContextObserver observer(
      profile());
  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
      "service_worker/messaging/connect_to_worker/connect_and_post"));
  ASSERT_TRUE(extension);
  const int64_t version_id = observer.WaitForWorkerStarted();

  // Set idle timeout to 1 second.
  content::ServiceWorkerContext* context =
      util::GetServiceWorkerContextForExtensionId(extension->id(), profile());
  content::SetServiceWorkerIdleDelay(context, version_id, base::Seconds(1));

  // Load the tab with content script to open a Port to |extension|.
  ResultCatcher catcher;
  {
    content::WebContents* new_web_contents =
        AddTab(embedded_test_server()->GetURL("/extensions/test_file.html"));
    EXPECT_TRUE(new_web_contents);
  }
  // Catching the succeed test message means the service worker has been alive
  // for longer than 2 seconds.
  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();

  // The content script is sending messages over the long-lived channel every
  // 100ms. The service worker is still running at this point verifies that
  // sending messages prolongs the service worker lifetime.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(content::CheckServiceWorkerIsRunning(context, version_id));
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

}  // namespace extensions
