| // Copyright 2017 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 "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/frame_host/render_frame_host_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/url_loader_factory_getter.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/simple_url_loader.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/simple_url_loader_test_helper.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/network/public/interfaces/network_service.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| network::mojom::NetworkContextPtr CreateNetworkContext() { |
| network::mojom::NetworkContextPtr network_context; |
| network::mojom::NetworkContextParamsPtr context_params = |
| network::mojom::NetworkContextParams::New(); |
| GetNetworkService()->CreateNetworkContext(mojo::MakeRequest(&network_context), |
| std::move(context_params)); |
| return network_context; |
| } |
| |
| int LoadBasicRequestOnIOThread( |
| URLLoaderFactoryGetter* url_loader_factory_getter, |
| const GURL& url) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| |
| SimpleURLLoaderTestHelper simple_loader_helper; |
| // Wait for callback on UI thread to avoid nesting IO message loops. |
| simple_loader_helper.SetRunLoopQuitThread(BrowserThread::UI); |
| |
| std::unique_ptr<content::SimpleURLLoader> simple_loader = |
| content::SimpleURLLoader::Create(std::move(request), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| // |URLLoaderFactoryGetter::GetNetworkFactory()| can only be accessed on IO |
| // thread. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce( |
| [](content::SimpleURLLoader* loader, |
| URLLoaderFactoryGetter* factory_getter, |
| SimpleURLLoader::BodyAsStringCallback body_as_string_callback) { |
| loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| factory_getter->GetNetworkFactory(), |
| std::move(body_as_string_callback)); |
| }, |
| base::Unretained(simple_loader.get()), |
| base::Unretained(url_loader_factory_getter), |
| simple_loader_helper.GetCallback())); |
| |
| simple_loader_helper.WaitForCallback(); |
| return simple_loader->NetError(); |
| } |
| |
| } // namespace |
| |
| // This test source has been excluded from Android as Android doesn't have |
| // out-of-process Network Service. |
| class NetworkServiceRestartBrowserTest : public ContentBrowserTest { |
| public: |
| NetworkServiceRestartBrowserTest() { |
| scoped_feature_list_.InitAndEnableFeature(features::kNetworkService); |
| } |
| |
| void SetUpOnMainThread() override { |
| EXPECT_TRUE(embedded_test_server()->Start()); |
| ContentBrowserTest::SetUpOnMainThread(); |
| } |
| |
| GURL GetTestURL() const { |
| // Use '/echoheader' instead of '/echo' to avoid a disk_cache bug. |
| // See https://crbug.com/792255. |
| return embedded_test_server()->GetURL("/echoheader"); |
| } |
| |
| BrowserContext* browser_context() { |
| return shell()->web_contents()->GetBrowserContext(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkServiceRestartBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, |
| NetworkServiceProcessRecovery) { |
| network::mojom::NetworkContextPtr network_context = CreateNetworkContext(); |
| EXPECT_EQ(net::OK, LoadBasicRequest(network_context.get(), GetTestURL())); |
| EXPECT_TRUE(network_context.is_bound()); |
| EXPECT_FALSE(network_context.encountered_error()); |
| |
| // Crash the NetworkService process. Existing interfaces should receive error |
| // notifications at some point. |
| SimulateNetworkServiceCrash(); |
| // |network_context| will receive an error notification, but it's not |
| // guaranteed to have arrived at this point. Flush the pointer to make sure |
| // the notification has been received. |
| network_context.FlushForTesting(); |
| EXPECT_TRUE(network_context.is_bound()); |
| EXPECT_TRUE(network_context.encountered_error()); |
| // Make sure we could get |net::ERR_FAILED| with an invalid |network_context|. |
| EXPECT_EQ(net::ERR_FAILED, |
| LoadBasicRequest(network_context.get(), GetTestURL())); |
| |
| // NetworkService should restart automatically and return valid interface. |
| network::mojom::NetworkContextPtr network_context2 = CreateNetworkContext(); |
| EXPECT_EQ(net::OK, LoadBasicRequest(network_context2.get(), GetTestURL())); |
| EXPECT_TRUE(network_context2.is_bound()); |
| EXPECT_FALSE(network_context2.encountered_error()); |
| } |
| |
| // Make sure |StoragePartitionImpl::GetNetworkContext()| returns valid interface |
| // after crash. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, |
| StoragePartitionImplGetNetworkContext) { |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition(browser_context())); |
| |
| network::mojom::NetworkContext* old_network_context = |
| partition->GetNetworkContext(); |
| EXPECT_EQ(net::OK, LoadBasicRequest(old_network_context, GetTestURL())); |
| |
| // Crash the NetworkService process. Existing interfaces should receive error |
| // notifications at some point. |
| SimulateNetworkServiceCrash(); |
| // Flush the interface to make sure the error notification was received. |
| partition->FlushNetworkInterfaceForTesting(); |
| |
| // |partition->GetNetworkContext()| should return a valid new pointer after |
| // crash. |
| EXPECT_NE(old_network_context, partition->GetNetworkContext()); |
| EXPECT_EQ(net::OK, |
| LoadBasicRequest(partition->GetNetworkContext(), GetTestURL())); |
| } |
| |
| // Make sure |URLLoaderFactoryGetter| returns valid interface after crash. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, |
| URLLoaderFactoryGetterGetNetworkFactory) { |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition(browser_context())); |
| scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter = |
| partition->url_loader_factory_getter(); |
| EXPECT_EQ(net::OK, LoadBasicRequestOnIOThread(url_loader_factory_getter.get(), |
| GetTestURL())); |
| // Crash the NetworkService process. Existing interfaces should receive error |
| // notifications at some point. |
| SimulateNetworkServiceCrash(); |
| // Flush the interface to make sure the error notification was received. |
| partition->FlushNetworkInterfaceForTesting(); |
| url_loader_factory_getter->FlushNetworkInterfaceOnIOThreadForTesting(); |
| |
| // |url_loader_factory_getter| should be able to get a valid new pointer after |
| // crash. |
| EXPECT_EQ(net::OK, LoadBasicRequestOnIOThread(url_loader_factory_getter.get(), |
| GetTestURL())); |
| } |
| |
| // Make sure basic navigation works after crash. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, |
| NavigationURLLoaderBasic) { |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition(browser_context())); |
| |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"))); |
| |
| // Crash the NetworkService process. Existing interfaces should receive error |
| // notifications at some point. |
| SimulateNetworkServiceCrash(); |
| // Flush the interface to make sure the error notification was received. |
| partition->FlushNetworkInterfaceForTesting(); |
| partition->url_loader_factory_getter() |
| ->FlushNetworkInterfaceOnIOThreadForTesting(); |
| |
| EXPECT_TRUE( |
| NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html"))); |
| } |
| |
| class NetworkServiceRestartDisableWebSecurityTest |
| : public NetworkServiceRestartBrowserTest { |
| public: |
| NetworkServiceRestartDisableWebSecurityTest() {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Simulate a compromised renderer, otherwise the cross-origin request is |
| // blocked. |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| NetworkServiceRestartBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->RegisterRequestMonitor(base::BindRepeating( |
| &NetworkServiceRestartDisableWebSecurityTest::MonitorRequest, |
| base::Unretained(this))); |
| NetworkServiceRestartBrowserTest::SetUpOnMainThread(); |
| } |
| |
| RenderFrameHostImpl* main_frame() { |
| return static_cast<RenderFrameHostImpl*>( |
| shell()->web_contents()->GetMainFrame()); |
| } |
| |
| bool CheckCanLoadHttp(const std::string& relative_url) { |
| GURL test_url = embedded_test_server()->GetURL(relative_url); |
| std::string script( |
| "var xhr = new XMLHttpRequest();" |
| "xhr.open('GET', '"); |
| script += test_url.spec() + |
| "', true);" |
| "xhr.onload = function (e) {" |
| " if (xhr.readyState === 4) {" |
| " window.domAutomationController.send(xhr.status === 200);" |
| " }" |
| "};" |
| "xhr.onerror = function () {" |
| " window.domAutomationController.send(false);" |
| "};" |
| "xhr.send(null)"; |
| bool xhr_result = false; |
| // The JS call will fail if disallowed because the process will be killed. |
| bool execute_result = |
| ExecuteScriptAndExtractBool(shell(), script, &xhr_result); |
| return xhr_result && execute_result; |
| } |
| |
| // Called by |embedded_test_server()|. |
| void MonitorRequest(const net::test_server::HttpRequest& request) { |
| last_request_relative_url_ = request.relative_url; |
| } |
| |
| std::string last_request_relative_url() const { |
| return last_request_relative_url_; |
| } |
| |
| private: |
| std::string last_request_relative_url_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetworkServiceRestartDisableWebSecurityTest); |
| }; |
| |
| // Make sure basic XHR works after crash. |
| IN_PROC_BROWSER_TEST_F(NetworkServiceRestartDisableWebSecurityTest, BasicXHR) { |
| StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition(browser_context())); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("foo.com", "/echo"))); |
| EXPECT_TRUE(CheckCanLoadHttp("/title1.html")); |
| EXPECT_EQ(last_request_relative_url(), "/title1.html"); |
| |
| // Crash the NetworkService process. Existing interfaces should receive error |
| // notifications at some point. |
| SimulateNetworkServiceCrash(); |
| // Flush the interface to make sure the error notification was received. |
| partition->FlushNetworkInterfaceForTesting(); |
| // Flush the interface to make sure the frame host has received error |
| // notification and the new URLLoaderFactoryBundle has been received by the |
| // frame. |
| main_frame()->FlushNetworkAndNavigationInterfacesForTesting(); |
| |
| EXPECT_TRUE(CheckCanLoadHttp("/title2.html")); |
| EXPECT_EQ(last_request_relative_url(), "/title2.html"); |
| } |
| |
| } // namespace content |