| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <algorithm> |
| #include <map> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/run_until.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/chrome_content_browser_client.h" |
| #include "chrome/browser/preloading/preloading_features.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tab_list/tab_list_interface.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "chrome/test/base/platform_browser_test.h" |
| #include "components/download/public/common/download_item.h" |
| #include "components/download/public/common/download_url_parameters.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/download_request_utils.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/network_service_util.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/service_worker_context.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/download_test_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/sync_call_restrictions.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/network_handle.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/embedded_test_server/install_default_websocket_handlers.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "services/network/public/cpp/url_loader_factory_builder.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "services/network/public/mojom/network_service_test.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/service_worker/service_worker_status_code.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h" |
| |
| namespace { |
| |
| using URLLoaderFactoryType = |
| content::ContentBrowserClient::URLLoaderFactoryType; |
| |
| #if !BUILDFLAG(ENABLE_EXTENSIONS_CORE) |
| // URLLoaderFactories for filesystem: and data: schemes are not backed by the |
| // network service. With that in mind, they should never be using the target |
| // network. |
| constexpr size_t kExpectedNonNetworkFactories = 2u; |
| #else |
| // On Desktop Android and Desktop platforms, when extensions are enabled, |
| // URLLoaderFactories for chrome-extension: scheme are also not backed by the |
| // network service, this means they will also not be using the target network. |
| constexpr size_t kExpectedNonNetworkFactories = 3u; |
| #endif |
| |
| class TestChromeContentBrowserClient : public ChromeContentBrowserClient { |
| public: |
| TestChromeContentBrowserClient() |
| : old_client_(content::SetBrowserClientForTesting(this)) {} |
| ~TestChromeContentBrowserClient() override { |
| content::SetBrowserClientForTesting(old_client_); |
| } |
| |
| void WillCreateURLLoaderFactory( |
| content::BrowserContext* browser_context, |
| content::RenderFrameHost* frame, |
| int render_process_id, |
| URLLoaderFactoryType type, |
| const url::Origin& request_initiator, |
| const net::IsolationInfo& isolation_info, |
| std::optional<int64_t> navigation_id, |
| ukm::SourceIdObj ukm_source_id, |
| network::URLLoaderFactoryBuilder& factory_builder, |
| mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>* |
| header_client, |
| bool* bypass_redirect_checks, |
| bool* disable_secure_dns, |
| network::mojom::URLLoaderFactoryOverridePtr* factory_override, |
| scoped_refptr<base::SequencedTaskRunner> navigation_response_task_runner, |
| bool is_for_network_service) override { |
| ChromeContentBrowserClient::WillCreateURLLoaderFactory( |
| browser_context, frame, render_process_id, type, request_initiator, |
| isolation_info, navigation_id, ukm_source_id, factory_builder, |
| header_client, bypass_redirect_checks, disable_secure_dns, |
| factory_override, navigation_response_task_runner, |
| is_for_network_service); |
| factory_bound_networks_[type].push_back( |
| factory_builder.target_network_for_testing()); |
| factory_is_for_network_service_[type].push_back(is_for_network_service); |
| } |
| |
| bool HasBoundNetwork(URLLoaderFactoryType type, |
| net::handles::NetworkHandle network) const { |
| return CountBoundNetwork(type, network) > 0; |
| } |
| |
| size_t CountBoundNetwork(URLLoaderFactoryType type, |
| net::handles::NetworkHandle network) const { |
| auto it = factory_bound_networks_.find(type); |
| if (it != factory_bound_networks_.end()) { |
| return std::ranges::count(it->second, network); |
| } |
| return 0; |
| } |
| |
| size_t CountBoundNetwork(net::handles::NetworkHandle network) const { |
| size_t count = 0; |
| for (const auto& [type, networks] : factory_bound_networks_) { |
| count += std::ranges::count(networks, network); |
| } |
| return count; |
| } |
| |
| bool HasIsForNetworkService(URLLoaderFactoryType type, bool expected) const { |
| auto it = factory_is_for_network_service_.find(type); |
| if (it != factory_is_for_network_service_.end()) { |
| return std::ranges::contains(it->second, expected); |
| } |
| return false; |
| } |
| |
| private: |
| raw_ptr<content::ContentBrowserClient> old_client_; |
| std::map<URLLoaderFactoryType, std::vector<net::handles::NetworkHandle>> |
| factory_bound_networks_; |
| std::map<URLLoaderFactoryType, std::vector<bool>> |
| factory_is_for_network_service_; |
| }; |
| |
| constexpr auto kAllURLLoaderFactoryTypes = std::to_array({ |
| URLLoaderFactoryType::kNavigation, |
| URLLoaderFactoryType::kDownload, |
| URLLoaderFactoryType::kDocumentSubResource, |
| URLLoaderFactoryType::kWorkerMainResource, |
| URLLoaderFactoryType::kWorkerSubResource, |
| URLLoaderFactoryType::kServiceWorkerScript, |
| URLLoaderFactoryType::kServiceWorkerSubResource, |
| URLLoaderFactoryType::kPrefetch, |
| URLLoaderFactoryType::kDevTools, |
| URLLoaderFactoryType::kEarlyHints, |
| }); |
| |
| struct ExpectedFactoryCounts { |
| size_t navigation = 0; |
| size_t download = 0; |
| size_t document_subresource = 0; |
| size_t worker_main_resource = 0; |
| size_t worker_subresource = 0; |
| size_t service_worker_script = 0; |
| size_t service_worker_subresource = 0; |
| size_t prefetch = 0; |
| size_t devtools = 0; |
| size_t early_hints = 0; |
| // The number of URLLoaderFactories that are not targeting a specific network |
| // is always expected to be the ones for schemes that are not supported by the |
| // network service. There is no need to specify this in each test. |
| size_t invalid_network_handle = kExpectedNonNetworkFactories; |
| |
| size_t count_for(URLLoaderFactoryType type) const { |
| switch (type) { |
| case URLLoaderFactoryType::kNavigation: |
| return navigation; |
| case URLLoaderFactoryType::kDownload: |
| return download; |
| case URLLoaderFactoryType::kDocumentSubResource: |
| return document_subresource; |
| case URLLoaderFactoryType::kWorkerMainResource: |
| return worker_main_resource; |
| case URLLoaderFactoryType::kWorkerSubResource: |
| return worker_subresource; |
| case URLLoaderFactoryType::kServiceWorkerScript: |
| return service_worker_script; |
| case URLLoaderFactoryType::kServiceWorkerSubResource: |
| return service_worker_subresource; |
| case URLLoaderFactoryType::kPrefetch: |
| return prefetch; |
| case URLLoaderFactoryType::kDevTools: |
| return devtools; |
| case URLLoaderFactoryType::kEarlyHints: |
| return early_hints; |
| } |
| } |
| }; |
| |
| std::string_view URLLoaderFactoryTypeToString(URLLoaderFactoryType type) { |
| switch (type) { |
| case URLLoaderFactoryType::kNavigation: |
| return "kNavigation"; |
| case URLLoaderFactoryType::kDownload: |
| return "kDownload"; |
| case URLLoaderFactoryType::kDocumentSubResource: |
| return "kDocumentSubResource"; |
| case URLLoaderFactoryType::kWorkerMainResource: |
| return "kWorkerMainResource"; |
| case URLLoaderFactoryType::kWorkerSubResource: |
| return "kWorkerSubResource"; |
| case URLLoaderFactoryType::kServiceWorkerScript: |
| return "kServiceWorkerScript"; |
| case URLLoaderFactoryType::kServiceWorkerSubResource: |
| return "kServiceWorkerSubResource"; |
| case URLLoaderFactoryType::kPrefetch: |
| return "kPrefetch"; |
| case URLLoaderFactoryType::kDevTools: |
| return "kDevTools"; |
| case URLLoaderFactoryType::kEarlyHints: |
| return "kEarlyHints"; |
| } |
| } |
| |
| void VerifyFactoryCounts(const TestChromeContentBrowserClient& test_client, |
| net::handles::NetworkHandle target_network, |
| const ExpectedFactoryCounts& expected) { |
| size_t total_expected_bound_network = 0; |
| |
| for (auto type : kAllURLLoaderFactoryTypes) { |
| SCOPED_TRACE(testing::Message() |
| << "\n" |
| << "type = " << URLLoaderFactoryTypeToString(type) << "\n"); |
| size_t expected_count = expected.count_for(type); |
| EXPECT_EQ(expected_count, |
| test_client.CountBoundNetwork(type, target_network)); |
| total_expected_bound_network += expected_count; |
| |
| if (expected_count > 0 && |
| type != URLLoaderFactoryType::kWorkerSubResource) { |
| EXPECT_TRUE(test_client.HasIsForNetworkService(type, true)); |
| } |
| } |
| |
| EXPECT_EQ(total_expected_bound_network, |
| test_client.CountBoundNetwork(target_network)); |
| EXPECT_EQ(expected.invalid_network_handle, |
| test_client.CountBoundNetwork(net::handles::kInvalidNetworkHandle)); |
| } |
| |
| } // namespace |
| |
| /** |
| * Given a target_network X, multi-network browser test emulate target network |
| * binding in the following way: |
| * 1. EmbeddedTestServer starts listening on 127.0.0.X:PORT |
| * 2. Browser issues requests to http://127.0.0.1:PORT/..., with |
| * target_network = X (set in WebContents::CreateParams) |
| * 3. Socket layer translates requests for 127.0.0.1 to 127.0.0.X, iff the |
| * socket layer receives the target_network = X from the NetworkService API |
| * being tested. |
| * 4. The request then succeeds because it can reach EmbeddedTestServer on |
| * 127.0.0.X:PORT. This happens only if the target network has been |
| * correctly propagated all the way from the WebContents to the socket layer. |
| * |
| * Example: |
| * Browser / WebContents (target_network = X) |
| * | |
| * v |
| * URLLoaderFactory (target_network = X) |
| * | |
| * v |
| * net::URLRequest (target_network = X) |
| * | |
| * v |
| * TCPSocketPosix / UDPSocketPosix (bound_network_ = X) |
| * | |
| * +---> BindToNetwork(X) |
| * | | |
| * | v |
| * | Emulation ON: No-op / return OK |
| * | |
| * +---> Connect("127.0.0.1", PORT) |
| * | |
| * v |
| * Emulation ON & bound_network_ != default network |
| * Translate destination to "127.0.0.X", PORT |
| * | |
| * v |
| * Connects to EmbeddedTestServer (127.0.0.X:PORT) [OK] |
| */ |
| |
| class MultiNetworkBrowserTest : public PlatformBrowserTest { |
| public: |
| MultiNetworkBrowserTest() { |
| // Prewarm creates asynchronously, in the background, network-backed |
| // HTTP/HTTPS requests to google.com. These requests happen |
| // non-deterministically during startup. To isolate the test and keep |
| // assertions deterministic disable prewarming. |
| // TODO(crbug.com/539786691): Re-enable kPrewarm and filter out the |
| // prewarm requests instead. |
| feature_list_.InitAndDisableFeature(features::kPrewarm); |
| } |
| |
| void SetUpOnMainThread() override { |
| PlatformBrowserTest::SetUpOnMainThread(); |
| // When the network service runs out-of-process, all socket operations and |
| // network binding calls happen inside the dedicated Network Service utility |
| // process. We use the NetworkServiceTest Mojo IPC interface to toggle |
| // emulation in that remote process before starting any test requests. |
| // |
| // When the network service runs in-process, it shares the browser's address |
| // space, so we directly call |
| // net::handles::SetEmulateNetworkBindingForTesting. Although |
| // SetUpOnMainThread runs on the UI/main thread while sockets execute on the |
| // IO/network thread, posting navigation tasks to the IO thread forms a |
| // happens-before memory barrier, guaranteeing that the IO thread reads the |
| // updated flag without data races. |
| if (content::IsOutOfProcessNetworkService()) { |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test; |
| content::GetNetworkService()->BindTestInterfaceForTesting( |
| network_service_test.BindNewPipeAndPassReceiver()); |
| base::test::TestFuture<void> future; |
| network_service_test->SetEmulateNetworkBindingForTesting( |
| true, future.GetCallback()); |
| EXPECT_TRUE(future.Wait()); |
| } else { |
| net::handles::SetEmulateNetworkBindingForTesting(true); |
| } |
| } |
| |
| void TearDownOnMainThread() override { |
| // See comments in SetUpOnMainThread() for more details. |
| if (content::IsOutOfProcessNetworkService()) { |
| mojo::Remote<network::mojom::NetworkServiceTest> network_service_test; |
| content::GetNetworkService()->BindTestInterfaceForTesting( |
| network_service_test.BindNewPipeAndPassReceiver()); |
| base::test::TestFuture<void> future; |
| network_service_test->SetEmulateNetworkBindingForTesting( |
| false, future.GetCallback()); |
| EXPECT_TRUE(future.Wait()); |
| } else { |
| net::handles::SetEmulateNetworkBindingForTesting(false); |
| } |
| PlatformBrowserTest::TearDownOnMainThread(); |
| } |
| |
| std::string GetLoopbackAddressForNetwork( |
| net::handles::NetworkHandle network) { |
| CHECK_GE(network, 2); |
| CHECK_LE(network, 254); |
| return base::StringPrintf("127.0.0.%d", static_cast<int>(network)); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // MultiNetworkBrowserTest rely on the assumption that EmbeddedTestServer is |
| // not reachable via 127.0.0.1 if started on 127.0.0.X, with X != 1. To err on |
| // the safe side, this test confirms that this is really the case. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| UnreachableServerWithoutEmulateNetworkBinding) { |
| constexpr net::handles::NetworkHandle network = 2; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_FALSE(observer.last_navigation_succeeded()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, NavigationSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 2; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // 1 navigation factory for /title1.html, and 2 document subresource |
| // factories (1 for the initial empty document created during WebContents |
| // initialization, and 1 for /title1.html). |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, SubresourceSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 3; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| // Issue a subresource fetch request from the document. The request reuses the |
| // kDocumentSubResource URLLoaderFactory created during navigation commit. |
| EXPECT_EQ("ok", content::EvalJs( |
| web_contents.get(), |
| "fetch('/title1.html').then(r => r.ok ? 'ok' : 'fail')")); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // 1 navigation factory for /title1.html, and 2 document subresource |
| // factories (1 for the initial empty document created during WebContents |
| // initialization, and 1 for /title1.html). The subresource fetch does not |
| // create a new URLLoaderFactory, but instead reuses the |
| // kDocumentSubResource URLLoaderFactory created during navigation commit. |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, DownloadSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 4; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir download_dir; |
| ASSERT_TRUE(download_dir.CreateUniqueTempDir()); |
| |
| content::DownloadManager* download_manager = |
| GetProfile()->GetDownloadManager(); |
| auto params = |
| content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame( |
| web_contents.get(), url, TRAFFIC_ANNOTATION_FOR_TESTS); |
| params->set_file_path( |
| download_dir.GetPath().AppendASCII("test_download.html")); |
| params->set_transient(true); |
| // Necessary to prevent the download from triggering checks for whether the |
| // download is allowed. |
| params->set_content_initiated(false); |
| |
| content::DownloadTestObserverTerminal download_observer( |
| download_manager, 1, |
| content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL); |
| download_manager->DownloadUrl(std::move(params)); |
| download_observer.WaitForFinished(); |
| EXPECT_EQ(1u, download_observer.NumDownloadsSeenInState( |
| download::DownloadItem::COMPLETE)); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| {.download = 1, |
| // We are not triggering a navigation, so the kDocumentSubResource |
| // URLLoaderFactories are not created. Only a single URLLoaderFactory for |
| // kDownload is created. |
| .invalid_network_handle = 0}); |
| } |
| |
| class PopupTestWebContentsDelegate : public content::WebContentsDelegate { |
| public: |
| explicit PopupTestWebContentsDelegate( |
| content::BrowserContext* browser_context) |
| : browser_context_(browser_context) {} |
| |
| content::WebContents* OpenURLFromTab( |
| content::WebContents* source, |
| const content::OpenURLParams& params, |
| base::OnceCallback<void(content::NavigationHandle&)> |
| navigation_handle_callback) override { |
| content::WebContents::CreateParams create_params(browser_context_, |
| source->GetSiteInstance()); |
| create_params.target_network = source->GetTargetNetwork(); |
| opened_contents_ = content::WebContents::Create(create_params); |
| content::TestNavigationObserver observer(opened_contents_.get()); |
| content::NavigationController::LoadURLParams load_params(params.url); |
| opened_contents_->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| return opened_contents_.get(); |
| } |
| |
| content::WebContents* AddNewContents( |
| content::WebContents* source, |
| std::unique_ptr<content::WebContents> new_contents, |
| const GURL& target_url, |
| WindowOpenDisposition disposition, |
| const blink::mojom::WindowFeatures& window_features, |
| bool user_gesture, |
| bool* was_blocked) override { |
| opened_contents_ = std::move(new_contents); |
| return opened_contents_.get(); |
| } |
| |
| std::unique_ptr<content::WebContents> opened_contents_; |
| raw_ptr<content::BrowserContext> browser_context_; |
| }; |
| |
| // Tests that when a popup is created by clicking on a link, the new tab |
| // correctly inherits target_network from the opener. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| OpenLinkInPopupSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 5; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| PopupTestWebContentsDelegate delegate(GetProfile()); |
| web_contents->SetDelegate(&delegate); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| content::OpenURLParams open_params( |
| url, content::Referrer(), WindowOpenDisposition::NEW_POPUP, |
| ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false); |
| content::WebContents* popup_contents = |
| web_contents->OpenURL(open_params, /*navigation_handle_callback=*/{}); |
| ASSERT_TRUE(popup_contents); |
| EXPECT_EQ(popup_contents, delegate.opened_contents_.get()); |
| EXPECT_EQ(network, popup_contents->GetTargetNetwork()); |
| content::WaitForLoadStop(popup_contents); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // We are triggering 2 navigations, so we expect double the amount of |
| // URLLoaderFactories to be created. |
| {.navigation = 2, |
| .document_subresource = 4, |
| .invalid_network_handle = 2 * kExpectedNonNetworkFactories}); |
| } |
| |
| // Tests that when a popup is created by clicking on a link with rel=noopener, |
| // the new tab correctly inherits target_network from the opener, even though |
| // the opener relationship is later dropped. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| OpenLinkWithRelNoOpenerSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 12; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| PopupTestWebContentsDelegate delegate(GetProfile()); |
| web_contents->SetDelegate(&delegate); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| content::OpenURLParams open_params( |
| url, content::Referrer(), WindowOpenDisposition::NEW_POPUP, |
| ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false); |
| open_params.has_rel_opener = false; |
| content::WebContents* popup_contents = |
| web_contents->OpenURL(open_params, /*navigation_handle_callback=*/{}); |
| ASSERT_TRUE(popup_contents); |
| EXPECT_EQ(network, popup_contents->GetTargetNetwork()); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // We trigger 2 navigations: one for the main window; the other for the |
| // popup window, triggered via WebContents::OpenURL with |
| // `has_rel_opener = false`. Because OpenURLFromTab creates a standalone |
| // WebContents via WebContents::Create, an initial empty document is |
| // created for each window (not only the main window), resulting in a |
| // total of 4 document subresource factories (2 for main window, 2 for |
| // popup window). |
| {.navigation = 2, |
| .document_subresource = 4, |
| .invalid_network_handle = 2 * kExpectedNonNetworkFactories}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| DedicatedWorkerSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 6; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| [](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == "/worker.js") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->set_content( |
| "importScripts('/imported.js');\n" |
| "fetch('/fetch.txt').then(() => postMessage('done'));\n"); |
| return response; |
| } |
| if (request.relative_url == "/imported.js" || |
| request.relative_url == "/fetch.txt") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->set_content("// ok"); |
| return response; |
| } |
| return nullptr; |
| })); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| |
| EXPECT_EQ("done", |
| content::EvalJs(web_contents.get(), |
| "new Promise(resolve => {" |
| " const w = new Worker('/worker.js');" |
| " w.onerror = (e) => resolve('err: ' + e.message);" |
| " w.onmessage = (e) => resolve(e.data);" |
| "})")); |
| |
| VerifyFactoryCounts(test_client, network, |
| {.navigation = 1, |
| .document_subresource = 2, |
| .worker_main_resource = 1, |
| .worker_subresource = 1}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, PrefetchSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 7; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| EXPECT_TRUE(content::EvalJs(web_contents.get(), |
| "const l = document.createElement('link'); l.rel " |
| "= 'prefetch'; l.href = " |
| "'/title2.html'; document.head.appendChild(l);") |
| .is_ok()); |
| |
| VerifyFactoryCounts(test_client, network, |
| // 1 navigation factory for /title1.html, and 2 document |
| // subresource factories (1 for the initial empty document |
| // created during WebContents initialization, and 1 for |
| // /title1.html). The subresource fetch does not create a |
| // new URLLoaderFactory, but instead reuses the |
| // kDocumentSubResource URLLoaderFactory created during |
| // navigation commit. |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| // This test is not prescriptive, but rather demonstrates that service workers |
| // currently ignores the target_network. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| ServiceWorkerIgnoresTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 8; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| [](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == "/service_worker/generated_sw.js") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->set_content( |
| "self.addEventListener('install', event => {" |
| "event.waitUntil(" |
| "fetch('/sw_fetch.txt')" |
| ".then(response => {" |
| "if (!response.ok) {" |
| "throw new Error('status ' + response.status);" |
| "}" |
| "return response.text();" |
| "})" |
| ");" |
| "});"); |
| |
| return response; |
| } |
| if (request.relative_url == "/sw_fetch.txt") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/plain"); |
| response->set_content("ok"); |
| return response; |
| } |
| return nullptr; |
| })); |
| |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| // Service worker script requests pass frame == nullptr to |
| // WillCreateURLLoaderFactory, making it currently impossible to retrieve the |
| // target_network from the frame. Because the service worker factory ignores |
| // the target network, its request connects to 127.0.0.1 instead of 127.0.0.8, |
| // failing to reach the test server listening on 127.0.0.8. |
| EXPECT_EQ( |
| "err", |
| content::EvalJs( |
| web_contents.get(), |
| "navigator.serviceWorker.register('/service_worker/generated_sw.js')" |
| ".then(() => 'ok').catch(e => 'err')")); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| {.navigation = 1, |
| .document_subresource = 2, |
| // Service worker script requests pass frame == nullptr to |
| // WillCreateURLLoaderFactory, creating 1 kServiceWorkerScript factory |
| // and 1 non-network factory unbound to target_network before the script |
| // fetch fails to reach the 127.0.0.8 server. |
| .invalid_network_handle = kExpectedNonNetworkFactories + 2u}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, WebSocketIgnoresTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 9; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| net::EmbeddedTestServer ws_server(net::EmbeddedTestServer::TYPE_HTTP); |
| ws_server.ServeFilesFromSourceDirectory("chrome/test/data"); |
| ASSERT_TRUE( |
| ws_server.Start(/*port=*/0, GetLoopbackAddressForNetwork(network))); |
| |
| GURL url = ws_server.GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| GURL ws_url = |
| net::test_server::GetWebSocketURL(ws_server, "127.0.0.1", "/echo"); |
| std::string script = content::JsReplace( |
| "new Promise(resolve => {" |
| " const ws = new WebSocket($1);" |
| " ws.onclose = () => resolve();" |
| "});", |
| ws_url.spec().c_str()); |
| EXPECT_TRUE(content::EvalJs(web_contents.get(), script).is_ok()); |
| |
| VerifyFactoryCounts(test_client, network, |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| // This test is not prescriptive, but rather demonstrates that |
| // WebRTCPeerConnections currently ignore the target_network. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| WebRTCPeerConnectionIgnoresTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 10; |
| // TODO(crbug.com/537268694): Consider supporting target_network in WebRTC. |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| EXPECT_TRUE(content::EvalJs(web_contents.get(), |
| "new RTCPeerConnection({iceServers: [{urls: " |
| "'stun:127.0.0.1:3478'}]});") |
| .is_ok()); |
| |
| VerifyFactoryCounts(test_client, network, |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, SubframeSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 11; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| content::TestNavigationObserver subframe_observer(web_contents.get()); |
| EXPECT_TRUE( |
| content::EvalJs( |
| web_contents.get(), |
| "const iframe = document.createElement('iframe'); iframe.src = " |
| "'/title2.html'; document.body.appendChild(iframe);") |
| .is_ok()); |
| subframe_observer.Wait(); |
| EXPECT_TRUE(subframe_observer.last_navigation_succeeded()); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // We trigger 2 navigations (1 for main frame, 1 for iframe). Main frame |
| // creates 2 document subresource factories (1 for initial empty document, |
| // 1 for /title1.html), while the iframe directly commits /title2.html, |
| // creating only 1 document subresource factory (total = 3). |
| {.navigation = 2, |
| .document_subresource = 3, |
| .invalid_network_handle = 2 * kExpectedNonNetworkFactories}); |
| } |
| |
| // Tests that renderer-initiated window creation correctly inherits |
| // target_network from opener_rfh in WebContentsImpl::CreateWithOpener. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, WindowOpenSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 16; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| PopupTestWebContentsDelegate delegate(GetProfile()); |
| web_contents->SetDelegate(&delegate); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| GURL popup_url = embedded_test_server()->GetURL("127.0.0.1", "/title2.html"); |
| content::TestNavigationObserver popup_observer(popup_url); |
| popup_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(content::ExecJs( |
| web_contents.get(), content::JsReplace("window.open($1);", popup_url))); |
| popup_observer.Wait(); |
| EXPECT_TRUE(popup_observer.last_navigation_succeeded()); |
| |
| ASSERT_TRUE(delegate.opened_contents_); |
| // Verifies that target_network_ is inherited from opener_rfh in |
| // WebContentsImpl::CreateWithOpener, along with window.opener. |
| EXPECT_EQ(network, delegate.opened_contents_->GetTargetNetwork()); |
| EXPECT_EQ(true, content::EvalJs(delegate.opened_contents_.get(), |
| "window.opener !== null")); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // We trigger 2 navigations: one for the main window; the other for the |
| // popup window, triggered via JS window.open(). The main window creates 2 |
| // document subresource factories (1 for the initial empty document, 1 |
| // for /title1.html), while the popup created with an opener only creates |
| // 1 document subresource factory for /title2.html (total = 3). |
| {.navigation = 2, |
| .document_subresource = 3, |
| .invalid_network_handle = 2 * kExpectedNonNetworkFactories}); |
| } |
| |
| // Tests that renderer-initiated window creation correctly inherits |
| // target_network from opener_rfh in WebContentsImpl::CreateWithOpener, even |
| // though the opener relationship is later dropped. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| WindowOpenNoOpenerSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 17; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| PopupTestWebContentsDelegate delegate(GetProfile()); |
| web_contents->SetDelegate(&delegate); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| GURL popup_url = embedded_test_server()->GetURL("127.0.0.1", "/title2.html"); |
| content::TestNavigationObserver popup_observer(popup_url); |
| popup_observer.StartWatchingNewWebContents(); |
| EXPECT_TRUE(content::ExecJs( |
| web_contents.get(), |
| content::JsReplace("window.open($1, '_blank', 'noopener');", popup_url))); |
| popup_observer.Wait(); |
| EXPECT_TRUE(popup_observer.last_navigation_succeeded()); |
| |
| ASSERT_TRUE(delegate.opened_contents_); |
| // Verifies that target_network_ is inherited from opener_rfh in |
| // WebContentsImpl::CreateWithOpener before the opener relationship is |
| // disowned, resulting in window.opener === null in JavaScript while |
| // preserving the target network. |
| EXPECT_EQ(network, delegate.opened_contents_->GetTargetNetwork()); |
| EXPECT_EQ(true, content::EvalJs(delegate.opened_contents_.get(), |
| "window.opener === null")); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // We trigger 2 navigations: one for main window; the other for popup via |
| // JS window.open(..., 'noopener')). Because 'noopener' disowns the opener |
| // relationship, the popup window is created in a separate |
| // BrowsingInstance with an initial empty document (about:blank) before |
| // navigating to /title2.html. Thus, 4 document subresource factories are |
| // created (2 for main window, 2 for popup window). |
| {.navigation = 2, |
| .document_subresource = 4, |
| .invalid_network_handle = 2 * kExpectedNonNetworkFactories}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, BeaconSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 13; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| EXPECT_EQ(true, content::EvalJs(web_contents.get(), |
| "navigator.sendBeacon('/beacon')")); |
| |
| VerifyFactoryCounts(test_client, network, |
| {.navigation = 1, .document_subresource = 2}); |
| } |
| |
| // This test is not prescriptive, but rather demonstrates that shared workers |
| // currently ignore the target_network for subresources. |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| SharedWorkerIgnoresTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 14; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| [](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == "/shared_worker.js") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("text/javascript"); |
| response->set_content( |
| "onconnect = function(e) {\n" |
| " var port = e.ports[0];\n" |
| " port.postMessage('msg');\n" |
| "};\n"); |
| return response; |
| } |
| return nullptr; |
| })); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| EXPECT_EQ("msg", |
| content::EvalJs(web_contents.get(), |
| "new Promise(resolve => {" |
| " const w = new SharedWorker('/shared_worker.js');" |
| " w.onerror = (e) => resolve('err: ' + e.message);" |
| " w.port.onmessage = (e) => resolve(e.data);" |
| "})")); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| {.navigation = 1, |
| .document_subresource = 2, |
| // Created to fetch the shared worker script (/shared_worker.js). |
| .worker_main_resource = 1, |
| // Worker subresource requests are unbound to target_network because |
| // SharedWorkerHost::CreateNetworkFactoryForSubresources passes frame = |
| // nullptr to WillCreateURLLoaderFactory. |
| .invalid_network_handle = kExpectedNonNetworkFactories + 1u}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(MultiNetworkBrowserTest, |
| PaymentRequestSetsTargetNetwork) { |
| TestChromeContentBrowserClient test_client; |
| |
| constexpr net::handles::NetworkHandle network = 15; |
| |
| content::WebContents::CreateParams create_params(GetProfile()); |
| create_params.target_network = network; |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| embedded_test_server()->RegisterRequestHandler(base::BindRepeating( |
| [](const net::test_server::HttpRequest& request) |
| -> std::unique_ptr<net::test_server::HttpResponse> { |
| if (request.relative_url == "/pay_manifest.json") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/json"); |
| response->set_content( |
| "{\"default_applications\": [\"/app_manifest.json\"]}"); |
| return response; |
| } |
| if (request.relative_url == "/app_manifest.json") { |
| auto response = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_code(net::HTTP_OK); |
| response->set_content_type("application/json"); |
| response->set_content("{\"name\": \"PayApp\"}"); |
| return response; |
| } |
| return nullptr; |
| })); |
| |
| ASSERT_TRUE(embedded_test_server()->Start( |
| /*port=*/0, GetLoopbackAddressForNetwork(network))); |
| GURL url = embedded_test_server()->GetURL("127.0.0.1", "/title1.html"); |
| |
| content::TestNavigationObserver observer(web_contents.get()); |
| content::NavigationController::LoadURLParams load_params(url); |
| web_contents->GetController().LoadURLWithParams(load_params); |
| observer.Wait(); |
| EXPECT_TRUE(observer.last_navigation_succeeded()); |
| |
| GURL pay_method_url = |
| embedded_test_server()->GetURL("127.0.0.1", "/pay_manifest.json"); |
| |
| std::string script = content::JsReplace( |
| "const req = new PaymentRequest([{supportedMethods: $1}], {" |
| " total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}}" |
| "});" |
| "req.canMakePayment();", |
| pay_method_url.spec()); |
| |
| // PaymentRequest.canMakePayment() triggers manifest downloads using |
| // CreateNetworkServiceDefaultFactory on RenderFrameHost. |
| EXPECT_TRUE(content::EvalJs(web_contents.get(), script).is_ok()); |
| |
| VerifyFactoryCounts( |
| test_client, network, |
| // 1 navigation factory for /title1.html, and 2 document subresource |
| // factories (1 for the initial empty document created during WebContents |
| // initialization, and 1 for /title1.html). Chrome's |
| // PaymentManifestDownloader does not reuse the existing URLLoaderFactory |
| // for subresources created during navigation commit. Instead, it uses a |
| // dedicated URLLoaderFactory for each payment manifest download (created |
| // via RenderFrameHost::CreateNetworkServiceDefaultFactory, which |
| // correctly propagates the target_network because it calls |
| // WillCreateURLLoaderFactory and passes that RenderFrameHost). |
| {.navigation = 1, .document_subresource = 3}); |
| } |