[go: nahoru, domu]

blob: fd607df8354483624ebe10e54d4d379fec73336c [file] [log] [blame]
// 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 <stddef.h>
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chrome/browser/extensions/api/permissions/permissions_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/service_worker_context_observer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/service_worker_test_helpers.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/service_worker/service_worker_test_utils.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "url/gurl.h"
namespace extensions {
namespace {
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kTestOpenerExtensionId[] = "adpghjkjicpfhcjicmiifjpbalaildpo";
constexpr char kTestOpenerExtensionUrl[] =
"chrome-extension://adpghjkjicpfhcjicmiifjpbalaildpo/";
constexpr char kTestOpenerExtensionRelativePath[] =
"service_worker/policy/opener_extension";
constexpr char kTestReceiverExtensionId[] = "eagjmgdicfmccfhiiihnaehbfheheidk";
constexpr char kTestReceiverExtensionUrl[] =
"chrome-extension://eagjmgdicfmccfhiiihnaehbfheheidk/";
constexpr char kTestReceiverExtensionRelativePath[] =
"service_worker/policy/receiver_extension";
constexpr char kPersistentPortConnectedMessage[] = "Persistent port connected";
constexpr char kPersistentPortDisconnectedMessage[] =
"Persistent port disconnected";
#endif
} // namespace
// Observer for an extension service worker to start and stop.
class TestServiceWorkerContextObserver
: public content::ServiceWorkerContextObserver {
public:
TestServiceWorkerContextObserver(content::ServiceWorkerContext* context,
const ExtensionId& extension_id)
: context_(context),
extension_url_(Extension::GetBaseURLFromExtensionId(extension_id)) {
scoped_observation_.Observe(context);
}
TestServiceWorkerContextObserver(const TestServiceWorkerContextObserver&) =
delete;
TestServiceWorkerContextObserver& operator=(
const TestServiceWorkerContextObserver&) = delete;
~TestServiceWorkerContextObserver() override = default;
// Sets the ID of an already-running worker. This is handy so this observer
// can be instantiated after the extension has already started.
// NOTE: If we move this class somewhere more central, we could streamline
// this a bit by having it check for the state of the worker during
// construction.
void SetRunningId(int64_t version_id) { running_version_id_ = version_id; }
void WaitForWorkerStart() {
started_run_loop_.Run();
EXPECT_TRUE(running_version_id_.has_value());
}
void WaitForWorkerStop() {
// OnVersionStoppedRunning() might have already cleared running_version_id_.
if (running_version_id_.has_value()) {
stopped_run_loop_.Run();
}
}
int64_t GetServiceWorkerVersionId() { return running_version_id_.value(); }
private:
// ServiceWorkerContextObserver:
void OnVersionStartedRunning(
int64_t version_id,
const content::ServiceWorkerRunningInfo& running_info) override {
if (running_info.scope != extension_url_) {
return;
}
running_version_id_ = version_id;
started_run_loop_.Quit();
}
void OnVersionStoppedRunning(int64_t version_id) override {
if (running_version_id_ == version_id) {
stopped_run_loop_.Quit();
}
running_version_id_ = absl::nullopt;
}
void OnDestruct(content::ServiceWorkerContext* context) override {
DCHECK(scoped_observation_.IsObserving());
scoped_observation_.Reset();
}
base::RunLoop stopped_run_loop_;
base::RunLoop started_run_loop_;
absl::optional<int64_t> running_version_id_;
base::ScopedObservation<content::ServiceWorkerContext,
content::ServiceWorkerContextObserver>
scoped_observation_{this};
raw_ptr<content::ServiceWorkerContext> context_ = nullptr;
GURL extension_url_;
};
class ServiceWorkerLifetimeKeepaliveBrowsertest : public ExtensionApiTest {
public:
ServiceWorkerLifetimeKeepaliveBrowsertest() = default;
ServiceWorkerLifetimeKeepaliveBrowsertest(
const ServiceWorkerLifetimeKeepaliveBrowsertest&) = delete;
ServiceWorkerLifetimeKeepaliveBrowsertest& operator=(
const ServiceWorkerLifetimeKeepaliveBrowsertest&) = delete;
~ServiceWorkerLifetimeKeepaliveBrowsertest() override = default;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(StartEmbeddedTestServer());
}
void TearDownOnMainThread() override {
ExtensionApiTest::TearDownOnMainThread();
// Some tests use SetTickClockForTesting() with `tick_clock_opener_` or
// `tick_clock_receiver_`. Restore the TickClock to the default now.
// This is required because the TickClock must outlive ServiceWorkerVersion,
// otherwise ServiceWorkerVersion will hold a dangling pointer.
content::ResetTickClockToDefaultForAllLiveServiceWorkerVersions(
GetServiceWorkerContext());
}
void TriggerTimeoutAndCheckActive(content::ServiceWorkerContext* context,
int64_t version_id) {
EXPECT_TRUE(
content::TriggerTimeoutAndCheckRunningState(context, version_id));
}
void TriggerTimeoutAndCheckStopped(content::ServiceWorkerContext* context,
int64_t version_id) {
EXPECT_FALSE(
content::TriggerTimeoutAndCheckRunningState(context, version_id));
}
base::SimpleTestTickClock tick_clock_opener_;
base::SimpleTestTickClock tick_clock_receiver_;
};
// The following tests are only relevant on ash.
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Loads two extensions that open a persistent port connection between each
// other and tests that their service worker will stop after kRequestTimeout (5
// minutes).
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
ServiceWorkersTimeOutWithoutPolicy) {
content::ServiceWorkerContext* context = GetServiceWorkerContext();
TestServiceWorkerContextObserver sw_observer_receiver_extension(
context, kTestReceiverExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestReceiverExtensionRelativePath));
sw_observer_receiver_extension.WaitForWorkerStart();
ExtensionTestMessageListener connect_listener(
kPersistentPortConnectedMessage);
connect_listener.set_extension_id(kTestReceiverExtensionId);
TestServiceWorkerContextObserver sw_observer_opener_extension(
context, kTestOpenerExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestOpenerExtensionRelativePath));
sw_observer_opener_extension.WaitForWorkerStart();
ASSERT_TRUE(connect_listener.WaitUntilSatisfied());
int64_t service_worker_receiver_id =
sw_observer_receiver_extension.GetServiceWorkerVersionId();
int64_t service_worker_opener_id =
sw_observer_opener_extension.GetServiceWorkerVersionId();
// Advance clock and check that the receiver service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_receiver_id,
&tick_clock_receiver_);
TriggerTimeoutAndCheckStopped(context, service_worker_receiver_id);
sw_observer_receiver_extension.WaitForWorkerStop();
// Advance clock and check that the opener service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_opener_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, service_worker_opener_id);
sw_observer_opener_extension.WaitForWorkerStop();
}
// Tests that the service workers will not stop if both extensions are
// allowlisted via policy and the port is not closed.
// TODO(https://crbug.com/1454339): Flakes on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_ServiceWorkersDoNotTimeOutWithPolicy \
DISABLED_ServiceWorkersDoNotTimeOutWithPolicy
#else
#define MAYBE_ServiceWorkersDoNotTimeOutWithPolicy \
ServiceWorkersDoNotTimeOutWithPolicy
#endif
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
MAYBE_ServiceWorkersDoNotTimeOutWithPolicy) {
base::Value::List urls;
// Both extensions receive extended lifetime.
urls.Append(kTestOpenerExtensionUrl);
urls.Append(kTestReceiverExtensionUrl);
browser()->profile()->GetPrefs()->SetList(
pref_names::kExtendedBackgroundLifetimeForPortConnectionsToUrls,
std::move(urls));
content::ServiceWorkerContext* context = GetServiceWorkerContext();
TestServiceWorkerContextObserver sw_observer_receiver_extension(
context, kTestReceiverExtensionId);
const Extension* receiver_extension = LoadExtension(
test_data_dir_.AppendASCII(kTestReceiverExtensionRelativePath));
sw_observer_receiver_extension.WaitForWorkerStart();
ExtensionTestMessageListener connect_listener(
kPersistentPortConnectedMessage);
connect_listener.set_extension_id(kTestReceiverExtensionId);
TestServiceWorkerContextObserver sw_observer_opener_extension(
context, kTestOpenerExtensionId);
const Extension* opener_extension = LoadExtension(
test_data_dir_.AppendASCII(kTestOpenerExtensionRelativePath));
sw_observer_opener_extension.WaitForWorkerStart();
ASSERT_TRUE(connect_listener.WaitUntilSatisfied());
int64_t service_worker_receiver_id =
sw_observer_receiver_extension.GetServiceWorkerVersionId();
int64_t service_worker_opener_id =
sw_observer_opener_extension.GetServiceWorkerVersionId();
// Advance clock and check that the receiver service worker did not stop.
content::AdvanceClockAfterRequestTimeout(context, service_worker_receiver_id,
&tick_clock_receiver_);
TriggerTimeoutAndCheckActive(context, service_worker_receiver_id);
// Advance clock and check that the opener service worker did not stop.
content::AdvanceClockAfterRequestTimeout(context, service_worker_opener_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckActive(context, service_worker_opener_id);
// Clean up: stop running service workers before test end.
base::test::TestFuture<void> future_1;
content::StopServiceWorkerForScope(context, receiver_extension->url(),
future_1.GetCallback());
EXPECT_TRUE(future_1.Wait());
base::test::TestFuture<void> future_2;
content::StopServiceWorkerForScope(context, opener_extension->url(),
future_2.GetCallback());
EXPECT_TRUE(future_2.Wait());
}
// Tests that the extended lifetime only lasts as long as there is a persistent
// port connection. If the port is closed (by one of the service workers
// stopping), the other service worker will also stop, even if it received an
// extended lifetime.
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
ServiceWorkersTimeOutWhenOnlyOneHasExtendedLifetime) {
base::Value::List urls;
// Opener extension will receive extended lifetime because it connects to a
// policy allowlisted extension.
urls.Append(kTestReceiverExtensionUrl);
browser()->profile()->GetPrefs()->SetList(
pref_names::kExtendedBackgroundLifetimeForPortConnectionsToUrls,
std::move(urls));
content::ServiceWorkerContext* context = GetServiceWorkerContext();
TestServiceWorkerContextObserver sw_observer_receiver_extension(
context, kTestReceiverExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestReceiverExtensionRelativePath));
sw_observer_receiver_extension.WaitForWorkerStart();
ExtensionTestMessageListener connect_listener(
kPersistentPortConnectedMessage);
connect_listener.set_extension_id(kTestReceiverExtensionId);
TestServiceWorkerContextObserver sw_observer_opener_extension(
context, kTestOpenerExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestOpenerExtensionRelativePath));
sw_observer_opener_extension.WaitForWorkerStart();
ASSERT_TRUE(connect_listener.WaitUntilSatisfied());
int64_t service_worker_receiver_id =
sw_observer_receiver_extension.GetServiceWorkerVersionId();
int64_t service_worker_opener_id =
sw_observer_opener_extension.GetServiceWorkerVersionId();
ExtensionTestMessageListener disconnect_listener(
kPersistentPortDisconnectedMessage);
disconnect_listener.set_extension_id(kTestOpenerExtensionId);
// Advance clock and check that the receiver service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_receiver_id,
&tick_clock_receiver_);
TriggerTimeoutAndCheckStopped(context, service_worker_receiver_id);
// Wait for the receiver SW to be closed in order for the port to be
// disconnected and the opener SW losing extended lifetime.
sw_observer_receiver_extension.WaitForWorkerStop();
// Wait for port to close in the opener extension.
ASSERT_TRUE(disconnect_listener.WaitUntilSatisfied());
// Advance clock and check that the opener service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_opener_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, service_worker_opener_id);
sw_observer_opener_extension.WaitForWorkerStop();
}
// Tests that the service workers will stop if both extensions are allowlisted
// via policy and the port is disconnected.
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
ServiceWorkersTimeOutWhenPortIsDisconnected) {
base::Value::List urls;
// Both extensions receive extended lifetime.
urls.Append(kTestReceiverExtensionUrl);
urls.Append(kTestOpenerExtensionUrl);
browser()->profile()->GetPrefs()->SetList(
pref_names::kExtendedBackgroundLifetimeForPortConnectionsToUrls,
std::move(urls));
content::ServiceWorkerContext* context = GetServiceWorkerContext();
TestServiceWorkerContextObserver sw_observer_receiver_extension(
context, kTestReceiverExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestReceiverExtensionRelativePath));
sw_observer_receiver_extension.WaitForWorkerStart();
ExtensionTestMessageListener connect_listener(
kPersistentPortConnectedMessage);
connect_listener.set_extension_id(kTestReceiverExtensionId);
TestServiceWorkerContextObserver sw_observer_opener_extension(
context, kTestOpenerExtensionId);
LoadExtension(test_data_dir_.AppendASCII(kTestOpenerExtensionRelativePath));
sw_observer_opener_extension.WaitForWorkerStart();
ASSERT_TRUE(connect_listener.WaitUntilSatisfied());
int64_t service_worker_receiver_id =
sw_observer_receiver_extension.GetServiceWorkerVersionId();
int64_t service_worker_opener_id =
sw_observer_opener_extension.GetServiceWorkerVersionId();
ExtensionTestMessageListener disconnect_listener(
kPersistentPortDisconnectedMessage);
disconnect_listener.set_extension_id(kTestOpenerExtensionId);
// Disconnect the port from the receiver extension.
constexpr char kDisconnectScript[] = R"(port.disconnect();)";
BackgroundScriptExecutor script_executor(browser()->profile());
script_executor.ExecuteScriptAsync(
kTestReceiverExtensionId, kDisconnectScript,
BackgroundScriptExecutor::ResultCapture::kNone,
browsertest_util::ScriptUserActivation::kDontActivate);
// Wait for port to close in the opener extension.
ASSERT_TRUE(disconnect_listener.WaitUntilSatisfied());
// Advance clock and check that the receiver service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_receiver_id,
&tick_clock_receiver_);
TriggerTimeoutAndCheckStopped(context, service_worker_receiver_id);
// Wait for the receiver SW to be closed.
sw_observer_receiver_extension.WaitForWorkerStop();
// Advance clock and check that the opener service worker stopped.
content::AdvanceClockAfterRequestTimeout(context, service_worker_opener_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, service_worker_opener_id);
sw_observer_opener_extension.WaitForWorkerStop();
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Tests that certain API functions can keep the service worker alive
// indefinitely.
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
KeepalivesForCertainExtensionFunctions) {
static constexpr char kManifest[] =
R"({
"name": "test extension",
"manifest_version": 3,
"background": {"service_worker": "background.js"},
"version": "0.1",
"optional_permissions": ["tabs"]
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// blank");
// Load up the extension and wait for the worker to start.
service_worker_test_utils::TestRegistrationObserver registration_observer(
profile());
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
// We explicitly wait for the worker to be activated. Otherwise, the
// activation event might still be running when we advance the timer, causing
// the worker to be killed for the activation event timing out.
registration_observer.WaitForWorkerActivated();
int64_t version_id = registration_observer.GetServiceWorkerVersionId();
// Inject a script that will trigger chrome.permissions.request() and then
// return. When permissions.request() resolves, it will send a message.
static constexpr char kTriggerPrompt[] =
R"(chrome.test.runWithUserGesture(() => {
chrome.permissions.request({permissions: ['tabs']}).then(() => {
chrome.test.sendMessage('resolved');
});
chrome.test.sendScriptResult('success');
});)";
// Programmatically control the permissions request result. This allows us
// to control when it is resolved.
auto dialog_action_reset =
PermissionsRequestFunction::SetDialogActionForTests(
PermissionsRequestFunction::DialogAction::kProgrammatic);
base::Value result = BackgroundScriptExecutor::ExecuteScript(
profile(), extension->id(), kTriggerPrompt,
BackgroundScriptExecutor::ResultCapture::kSendScriptResult);
EXPECT_EQ("success", result);
content::ServiceWorkerContext* context = GetServiceWorkerContext();
// Right now, the permissions request should be pending. Since
// `permissions.request()` is specified as a function that can keep the
// extension worker alive indefinitely, advancing the clock and triggering the
// timeout should not result in a worker kill.
content::AdvanceClockAfterRequestTimeout(context, version_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckActive(context, version_id);
{
ExtensionTestMessageListener listener("resolved");
// Resolve the pending dialog and wait for the resulting message.
PermissionsRequestFunction::ResolvePendingDialogForTests(false);
ASSERT_TRUE(listener.WaitUntilSatisfied());
// We also run a run loop here so that the keepalive from the
// test.sendMessage() call is resolved.
base::RunLoop().RunUntilIdle();
}
// Advance the timer again. This should result in the worker being stopped,
// since the permissions.request() function call is now completed.
content::AdvanceClockAfterRequestTimeout(context, version_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, version_id);
}
// Test the flow of an extension function resolving after an extension service
// worker has timed out and been terminated.
// Regression test for https://crbug.com/1453534.
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
ExtensionFunctionGetsResolvedAfterWorkerTermination) {
static constexpr char kManifest[] =
R"({
"name": "test extension",
"manifest_version": 3,
"background": {"service_worker": "background.js"},
"version": "0.1"
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "// blank");
// Load up the extension and wait for the worker to start.
service_worker_test_utils::TestRegistrationObserver registration_observer(
profile());
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
// We explicitly wait for the worker to be activated. Otherwise, the
// activation event might still be running when we advance the timer, causing
// the worker to be killed for the activation event timing out.
registration_observer.WaitForWorkerActivated();
int64_t version_id = registration_observer.GetServiceWorkerVersionId();
// Inject a trivial script that will call test.sendMessage(). This is a handy
// API because, by indicating the test will reply, we control when the
// function is resolved.
static constexpr char kScript[] =
"chrome.test.sendMessage('hello', () => {});";
ExtensionTestMessageListener message_listener("hello",
ReplyBehavior::kWillReply);
BackgroundScriptExecutor::ExecuteScriptAsync(profile(), extension->id(),
kScript);
ASSERT_TRUE(message_listener.WaitUntilSatisfied());
content::ServiceWorkerContext* context = GetServiceWorkerContext();
TestServiceWorkerContextObserver context_observer(context, extension->id());
context_observer.SetRunningId(version_id);
// Advance the request past the timeout. Since test.sendMessage() doesn't
// keep a worker alive indefinitely, the service worker should be terminated.
content::AdvanceClockAfterRequestTimeout(context, version_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, version_id);
// Wait for the worker to fully stop.
context_observer.WaitForWorkerStop();
// Reply to the extension (even though the worker is gone). This triggers
// the completion of the extension function, which would otherwise try to
// decrement the keepalive count of the worker. The worker was already
// terminated; it should gracefully handle this case (as opposed to crash).
message_listener.Reply("foo");
}
// Tests that an active debugger session will keep an extension service worker
// alive past its typical timeout.
IN_PROC_BROWSER_TEST_F(ServiceWorkerLifetimeKeepaliveBrowsertest,
DebuggerAttachKeepsServiceWorkerAlive) {
static constexpr char kManifest[] =
R"({
"name": "Debugger attach",
"manifest_version": 3,
"version": "0.1",
"permissions": ["debugger"],
"background": {
"service_worker": "background.js"
}
})";
// A simple background script that knows how to attach and detach a debugging
// session from a target (active) tab.
static constexpr char kBackgroundJs[] =
R"(let attachedTab;
async function attachToActiveTab() {
let tabs =
await chrome.tabs.query({active: true, currentWindow: true});
let tab = tabs[0];
await chrome.debugger.attach({tabId: tab.id}, '1.3');
attachedTab = tab;
chrome.test.sendScriptResult('attached');
}
async function detach() {
await chrome.debugger.detach({tabId: attachedTab.id});
chrome.test.sendScriptResult('detached');
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
// Load up the extension and wait for the worker to start.
service_worker_test_utils::TestRegistrationObserver registration_observer(
profile());
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// We explicitly wait for the worker to be activated. Otherwise, the
// activation event might still be running when we advance the timer, causing
// the worker to be killed for the activation event timing out.
registration_observer.WaitForWorkerActivated();
int64_t version_id = registration_observer.GetServiceWorkerVersionId();
// Open a new tab for the extension to attach a debugger to.
const GURL example_com =
embedded_test_server()->GetURL("example.com", "/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), example_com));
EXPECT_EQ(example_com, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
// Attach the extension debugger.
EXPECT_EQ("attached",
BackgroundScriptExecutor::ExecuteScript(
profile(), extension->id(), "attachToActiveTab();",
BackgroundScriptExecutor::ResultCapture::kSendScriptResult));
// Ensure the keepalive associated with sendScriptResult() has resolved.
base::RunLoop().RunUntilIdle();
content::ServiceWorkerContext* context = GetServiceWorkerContext();
// Since the extension has an active debugger session, it should not be
// terminated, even for going past the typical time limit.
content::AdvanceClockAfterRequestTimeout(context, version_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckActive(context, version_id);
// Have the extension detach its debugging session.
EXPECT_EQ("detached",
BackgroundScriptExecutor::ExecuteScript(
profile(), extension->id(), "detach();",
BackgroundScriptExecutor::ResultCapture::kSendScriptResult));
// Ensure the keepalive associated with sendScriptResult() has resolved.
base::RunLoop().RunUntilIdle();
// The extension service worker should now be terminated, since it no longer
// has an active debug session.
content::AdvanceClockAfterRequestTimeout(context, version_id,
&tick_clock_opener_);
TriggerTimeoutAndCheckStopped(context, version_id);
}
} // namespace extensions