| // Copyright 2020 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <algorithm> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "build/build_config.h" | 
 | #include "chrome/browser/ui/browser.h" | 
 | #include "chrome/common/privacy_budget/scoped_privacy_budget_config.h" | 
 | #include "chrome/test/base/chrome_test_utils.h" | 
 | #include "components/ukm/test_ukm_recorder.h" | 
 | #include "content/public/browser/worker_type.h" | 
 | #include "content/public/test/browser_test.h" | 
 | #include "content/public/test/browser_test_utils.h" | 
 | #include "net/test/embedded_test_server/http_request.h" | 
 | #include "net/test/embedded_test_server/http_response.h" | 
 | #include "services/metrics/public/cpp/ukm_builders.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | #if defined(OS_ANDROID) | 
 | #include "chrome/test/base/android/android_browser_test.h" | 
 | #else | 
 | #include "chrome/test/base/in_process_browser_test.h" | 
 | #endif | 
 |  | 
 | namespace { | 
 |  | 
 | class UkmWorkerBrowserTest : public PlatformBrowserTest { | 
 |  public: | 
 |   UkmWorkerBrowserTest() { | 
 |     privacy_budget_config_.Apply(test::ScopedPrivacyBudgetConfig::Parameters()); | 
 |   } | 
 |  | 
 |   void SetUpOnMainThread() override { | 
 |     test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); | 
 |   } | 
 |  | 
 |   content::WebContents* web_contents() { | 
 |     return chrome_test_utils::GetActiveWebContents(this); | 
 |   } | 
 |  | 
 |   const ukm::TestAutoSetUkmRecorder& test_ukm_recorder() const { | 
 |     return *test_ukm_recorder_; | 
 |   } | 
 |  | 
 |  private: | 
 |   test::ScopedPrivacyBudgetConfig privacy_budget_config_; | 
 |   std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(UkmWorkerBrowserTest, | 
 |                        SharedWorker_DocumentClientIdIsPlumbed) { | 
 |   using DocumentCreatedEntry = ukm::builders::DocumentCreated; | 
 |   using AddedEntry = ukm::builders::Worker_ClientAdded; | 
 |  | 
 |   embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
 |       [](const net::test_server::HttpRequest& request) | 
 |           -> std::unique_ptr<net::test_server::HttpResponse> { | 
 |         if (request.GetURL().path() != "/shared_worker_script") | 
 |           return nullptr; | 
 |         auto response = std::make_unique<net::test_server::BasicHttpResponse>(); | 
 |         response->set_content_type("text/javascript"); | 
 |         response->set_content( | 
 |             R"(self.onconnect = e => { e.ports[0].postMessage('DONE'); };)"); | 
 |         return response; | 
 |       })); | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   content::DOMMessageQueue messages; | 
 |  | 
 |   ASSERT_TRUE(content::NavigateToURL( | 
 |       web_contents(), embedded_test_server()->GetURL( | 
 |                           "/workers/create_shared_worker.html?worker_url=/" | 
 |                           "shared_worker_script"))); | 
 |  | 
 |   // Wait until the worker script is loaded and executed, to ensure the UKM is | 
 |   // logged. | 
 |   EXPECT_EQ("DONE", content::EvalJs( | 
 |                         browser()->tab_strip_model()->GetActiveWebContents(), | 
 |                         "waitForMessage();")); | 
 |  | 
 |   std::vector<const ukm::mojom::UkmEntry*> doc_created_entries = | 
 |       test_ukm_recorder().GetEntriesByName(DocumentCreatedEntry::kEntryName); | 
 |   EXPECT_EQ(1u, doc_created_entries.size()); | 
 |   const ukm::SourceId document_source_id = doc_created_entries[0]->source_id; | 
 |  | 
 |   // Check that we got the WorkerClientConnected event. | 
 |   std::vector<const ukm::mojom::UkmEntry*> connected_entries = | 
 |       test_ukm_recorder().GetEntriesByName(AddedEntry::kEntryName); | 
 |   EXPECT_EQ(1u, connected_entries.size()); | 
 |   const ukm::SourceId client_source_id = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[0], AddedEntry::kClientSourceIdName); | 
 |   const ukm::SourceId worker_source_id = connected_entries[0]->source_id; | 
 |   const int64_t worker_type = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[0], AddedEntry::kWorkerTypeName); | 
 |  | 
 |   // Check that we have two source IDs in play (namely that of the | 
 |   // client/document, and the SharedWorker) and that they are different. | 
 |   EXPECT_EQ(document_source_id, client_source_id); | 
 |   EXPECT_NE(worker_source_id, client_source_id); | 
 |  | 
 |   EXPECT_EQ(static_cast<int64_t>(WorkerType::kSharedWorker), worker_type); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(UkmWorkerBrowserTest, | 
 |                        ServiceWorker_DocumentClientIdIsPlumbed) { | 
 |   using DocumentCreatedEntry = ukm::builders::DocumentCreated; | 
 |   using AddedEntry = ukm::builders::Worker_ClientAdded; | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   ASSERT_TRUE(content::NavigateToURL( | 
 |       web_contents(), embedded_test_server()->GetURL( | 
 |                           "/service_worker/create_service_worker.html"))); | 
 |  | 
 |   // Wait until the worker script is loaded and executed, to ensure the UKM is | 
 |   // logged. | 
 |   EXPECT_EQ("DONE", EvalJs(web_contents(), | 
 |                            "register('fetch_event_respond_with_fetch.js');")); | 
 |  | 
 |   std::vector<const ukm::mojom::UkmEntry*> doc_created_entries = | 
 |       test_ukm_recorder().GetEntriesByName(DocumentCreatedEntry::kEntryName); | 
 |   ASSERT_EQ(1u, doc_created_entries.size()); | 
 |   const ukm::SourceId document_source_id = doc_created_entries[0]->source_id; | 
 |  | 
 |   // Check that we got the Worker.ClientAdded event. | 
 |   std::vector<const ukm::mojom::UkmEntry*> connected_entries = | 
 |       test_ukm_recorder().GetEntriesByName(AddedEntry::kEntryName); | 
 |   ASSERT_EQ(1u, connected_entries.size()); | 
 |   const ukm::SourceId client_source_id = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[0], AddedEntry::kClientSourceIdName); | 
 |   const ukm::SourceId worker_source_id = connected_entries[0]->source_id; | 
 |   const int64_t worker_type = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[0], AddedEntry::kWorkerTypeName); | 
 |  | 
 |   // Check that we have two source IDs in play (namely that of the | 
 |   // client/document, and the ServiceWorker) and that they are different. | 
 |   EXPECT_EQ(document_source_id, client_source_id); | 
 |   EXPECT_NE(worker_source_id, client_source_id); | 
 |  | 
 |   EXPECT_EQ(static_cast<int64_t>(WorkerType::kServiceWorker), worker_type); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(UkmWorkerBrowserTest, | 
 |                        ServiceWorker_DedicatedWorkerClientIdIsIgnored) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   ASSERT_TRUE(content::NavigateToURL( | 
 |       web_contents(), embedded_test_server()->GetURL( | 
 |                           "/service_worker/create_service_worker.html"))); | 
 |   EXPECT_EQ("DONE", EvalJs(web_contents(), | 
 |                            "register('fetch_event_respond_with_fetch.js');")); | 
 |  | 
 |   // Wait until the worker script is loaded and executed, to ensure the UKM is | 
 |   // logged. | 
 |   EXPECT_EQ("loaded", EvalJs(web_contents(), R"SCRIPT( | 
 |       const worker = new Worker('../workers/dedicated_worker.js'); | 
 |       const onmessage_promise = new Promise(r => worker.onmessage = r); | 
 |       async function waitForMessage() { | 
 |         const message = await onmessage_promise; | 
 |         return message.data; | 
 |       } | 
 |       waitForMessage(); | 
 |   )SCRIPT")); | 
 |  | 
 |   // Check that we only have the single Worker.ClientAdded event (for the | 
 |   // document). | 
 |   std::vector<const ukm::mojom::UkmEntry*> connected_entries = | 
 |       test_ukm_recorder().GetEntriesByName( | 
 |           ukm::builders::Worker_ClientAdded::kEntryName); | 
 |   EXPECT_EQ(1u, connected_entries.size()); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(UkmWorkerBrowserTest, | 
 |                        ServiceWorker_SharedWorkerClientIdIsPlumbed) { | 
 |   using AddedEntry = ukm::builders::Worker_ClientAdded; | 
 |  | 
 |   embedded_test_server()->RegisterRequestHandler(base::BindRepeating( | 
 |       [](const net::test_server::HttpRequest& request) | 
 |           -> std::unique_ptr<net::test_server::HttpResponse> { | 
 |         if (request.GetURL().path() != "/shared_worker_script") | 
 |           return nullptr; | 
 |         auto response = std::make_unique<net::test_server::BasicHttpResponse>(); | 
 |         response->set_content_type("text/javascript"); | 
 |         response->set_content( | 
 |             R"(self.onconnect = e => { e.ports[0].postMessage('DONE'); };)"); | 
 |         return response; | 
 |       })); | 
 |  | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   ASSERT_TRUE(content::NavigateToURL( | 
 |       web_contents(), embedded_test_server()->GetURL( | 
 |                           "/service_worker/create_service_worker.html"))); | 
 |  | 
 |   // Wait for the service worker to load. | 
 |   EXPECT_EQ("DONE", EvalJs(web_contents(), | 
 |                            "register('fetch_event_respond_with_fetch.js');")); | 
 |  | 
 |   // Wait for the shared worker to load. | 
 |   EXPECT_EQ("DONE", EvalJs(web_contents(), R"SCRIPT( | 
 |       const worker = new SharedWorker('/shared_worker_script'); | 
 |       const onmessage_promise = new Promise(r => worker.port.onmessage = r); | 
 |       async function waitForMessage() { | 
 |         const message = await onmessage_promise; | 
 |         return message.data; | 
 |       } | 
 |       waitForMessage(); | 
 |   )SCRIPT")); | 
 |  | 
 |   // Check that we have a Worker.ClientAdded event for all three pairs: | 
 |   // document-shared worker, document-service worker, and shared worker-service | 
 |   // worker. | 
 |   std::vector<const ukm::mojom::UkmEntry*> connected_entries = | 
 |       test_ukm_recorder().GetEntriesByName(AddedEntry::kEntryName); | 
 |   ASSERT_EQ(3u, connected_entries.size()); | 
 |  | 
 |   // Get the document and shared worker ids from the shared worker event. | 
 |   ukm::SourceId document_source_id = ukm::kInvalidSourceId; | 
 |   ukm::SourceId shared_worker_source_id = ukm::kInvalidSourceId; | 
 |   int shared_worker_event_index; | 
 |   for (int i = 0; i < 3; i++) { | 
 |     const int64_t worker_type = *test_ukm_recorder().GetEntryMetric( | 
 |         connected_entries[i], AddedEntry::kWorkerTypeName); | 
 |     if (worker_type == static_cast<int64_t>(WorkerType::kSharedWorker)) { | 
 |       EXPECT_EQ(document_source_id, ukm::kInvalidSourceId); | 
 |       EXPECT_EQ(shared_worker_source_id, ukm::kInvalidSourceId); | 
 |       document_source_id = *test_ukm_recorder().GetEntryMetric( | 
 |           connected_entries[i], AddedEntry::kClientSourceIdName); | 
 |       shared_worker_source_id = connected_entries[i]->source_id; | 
 |       shared_worker_event_index = i; | 
 |     } | 
 |   } | 
 |   ASSERT_NE(document_source_id, ukm::kInvalidSourceId); | 
 |   ASSERT_NE(shared_worker_source_id, ukm::kInvalidSourceId); | 
 |   EXPECT_NE(document_source_id, shared_worker_source_id); | 
 |  | 
 |   // Remove the shared worker event to leave just the service worker events. | 
 |   connected_entries.erase(connected_entries.begin() + | 
 |                           shared_worker_event_index); | 
 |  | 
 |   // Check the events contain the expected information without enforcing any | 
 |   // ordering. | 
 |   ukm::SourceId service_worker_source_id = connected_entries[0]->source_id; | 
 |   EXPECT_EQ(service_worker_source_id, connected_entries[1]->source_id); | 
 |  | 
 |   EXPECT_EQ(*test_ukm_recorder().GetEntryMetric(connected_entries[0], | 
 |                                                 AddedEntry::kWorkerTypeName), | 
 |             static_cast<int64_t>(WorkerType::kServiceWorker)); | 
 |   EXPECT_EQ(*test_ukm_recorder().GetEntryMetric(connected_entries[1], | 
 |                                                 AddedEntry::kWorkerTypeName), | 
 |             static_cast<int64_t>(WorkerType::kServiceWorker)); | 
 |  | 
 |   ukm::SourceId client_source_id_1 = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[0], AddedEntry::kClientSourceIdName); | 
 |   ukm::SourceId client_source_id_2 = *test_ukm_recorder().GetEntryMetric( | 
 |       connected_entries[1], AddedEntry::kClientSourceIdName); | 
 |  | 
 |   EXPECT_EQ( | 
 |       std::set<ukm::SourceId>({document_source_id, shared_worker_source_id}), | 
 |       std::set<ukm::SourceId>({client_source_id_1, client_source_id_2})); | 
 | } |