blob: 43e980737325776c85e1ee9bee9559dd48e72bc5 [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 "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/background_script_executor.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/1441221): Create test cases where we test "failures" like
// events not acking.
using ContextType = ExtensionBrowserTest::ContextType;
using EventMetricsBrowserTest = ExtensionBrowserTest;
// 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.ExtensionServiceWorker2",
ContextType::kServiceWorker,
{"Extensions.Events.DispatchToAckTime.ExtensionEventPage3"}},
// TODO(crbug.com/1441221): 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.ExtensionServiceWorker2"}},
{"Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker2",
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 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);
}
class EventMetricsDispatchToSenderBrowserTest
: public ExtensionBrowserTest,
public testing::WithParamInterface<ContextType> {
public:
EventMetricsDispatchToSenderBrowserTest() = default;
EventMetricsDispatchToSenderBrowserTest(
const EventMetricsDispatchToSenderBrowserTest&) = delete;
EventMetricsDispatchToSenderBrowserTest& operator=(
const EventMetricsDispatchToSenderBrowserTest&) = delete;
};
// Tests that the we do not emit event dispatch time metrics for webRequest
// events with active listeners.
IN_PROC_BROWSER_TEST_P(EventMetricsDispatchToSenderBrowserTest,
DispatchToSenderMetricTest) {
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 =
GetParam() == 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());
// We do not emit any dispatch histograms for webRequest events to active
// listeners.
histogram_tester.ExpectTotalCount(
"Extensions.Events.DispatchToAckTime.ExtensionServiceWorker2",
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
"Extensions.Events.DispatchToAckLongTime.ExtensionServiceWorker2",
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
"Extensions.Events.DidDispatchToAckSucceed.ExtensionServiceWorker2",
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
"Extensions.Events.DidDispatchToAckSucceed.ExtensionPage",
/*expected_count=*/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,
EventMetricsDispatchToSenderBrowserTest,
::testing::Values(ContextType::kPersistentBackground));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
EventMetricsDispatchToSenderBrowserTest,
::testing::Values(ContextType::kServiceWorker));
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());
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
};
// 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(web_contents()));
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