blob: 101798f013a1504f09c2bc7070745419569261aa [file] [log] [blame]
// 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/bind.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_environment_variable_override.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/network_service_instance_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/service_worker/embedded_worker_instance.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_core_observer.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/worker_host/test_shared_worker_service_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/network_service_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/commit_message_delayer.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/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/test/io_thread_shared_url_loader_factory_owner.h"
#include "content/test/storage_partition_test_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.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/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/network_service_test.mojom.h"
#include "third_party/blink/public/common/features.h"
#if BUILDFLAG(ENABLE_PLUGINS)
#include "content/public/test/ppapi_test_utils.h"
#endif
namespace content {
namespace {
using SharedURLLoaderFactoryGetterCallback =
base::OnceCallback<scoped_refptr<network::SharedURLLoaderFactory>()>;
mojo::PendingRemote<network::mojom::NetworkContext> CreateNetworkContext() {
mojo::PendingRemote<network::mojom::NetworkContext> network_context;
network::mojom::NetworkContextParamsPtr context_params =
network::mojom::NetworkContextParams::New();
context_params->cert_verifier_params = GetCertVerifierParams(
cert_verifier::mojom::CertVerifierCreationParams::New());
GetNetworkService()->CreateNetworkContext(
network_context.InitWithNewPipeAndPassReceiver(),
std::move(context_params));
return network_context;
}
int LoadBasicRequestOnUIThread(
network::mojom::URLLoaderFactory* url_loader_factory,
const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto request = std::make_unique<network::ResourceRequest>();
request->url = url;
SimpleURLLoaderTestHelper simple_loader_helper;
std::unique_ptr<network::SimpleURLLoader> simple_loader =
network::SimpleURLLoader::Create(std::move(request),
TRAFFIC_ANNOTATION_FOR_TESTS);
simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory, simple_loader_helper.GetCallback());
simple_loader_helper.WaitForCallback();
return simple_loader->NetError();
}
class ServiceWorkerStatusObserver : public ServiceWorkerContextCoreObserver {
public:
void WaitForStopped() {
if (stopped_)
return;
base::RunLoop loop;
callback_ = loop.QuitClosure();
loop.Run();
}
private:
void OnStopped(int64_t version_id) override {
stopped_ = true;
if (callback_)
std::move(callback_).Run();
}
bool stopped_ = false;
base::OnceClosure callback_;
};
} // namespace
class NetworkServiceRestartBrowserTest : public ContentBrowserTest {
public:
NetworkServiceRestartBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(ENABLE_PLUGINS)
// TODO(lukasza, kmoon): https://crbug.com/702993: Remove this dependency
// (and //ppapi/tests/corb_test_plugin.cc + BUILD.gn dependencies) once
// PDF support doesn't depend on PPAPI anymore.
ASSERT_TRUE(ppapi::RegisterCorbTestPlugin(command_line));
#endif
ContentBrowserTest::SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
embedded_test_server()->RegisterRequestMonitor(
base::BindRepeating(&NetworkServiceRestartBrowserTest::MonitorRequest,
base::Unretained(this)));
host_resolver()->AddRule("*", "127.0.0.1");
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();
}
RenderFrameHostImpl* main_frame() {
return static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetMainFrame());
}
bool CheckCanLoadHttp(Shell* shell, 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)";
// The JS call will fail if disallowed because the process will be killed.
return EvalJs(shell, script, EXECUTE_SCRIPT_USE_MANUAL_REPLY).ExtractBool();
}
// Will reuse the single opened windows through the test case.
bool CheckCanLoadHttpInWindowOpen(const std::string& relative_url) {
GURL test_url = embedded_test_server()->GetURL(relative_url);
std::string inject_script = base::StringPrintf(
"var xhr = new XMLHttpRequest();"
"xhr.open('GET', '%s', true);"
"xhr.onload = function (e) {"
" if (xhr.readyState === 4) {"
" window.opener.domAutomationController.send(xhr.status === 200);"
" }"
"};"
"xhr.onerror = function () {"
" window.opener.domAutomationController.send(false);"
"};"
"xhr.send(null)",
test_url.spec().c_str());
std::string window_open_script = base::StringPrintf(
"var new_window = new_window || window.open('');"
"var inject_script = document.createElement('script');"
"inject_script.innerHTML = \"%s\";"
"new_window.document.body.appendChild(inject_script);",
inject_script.c_str());
// The JS call will fail if disallowed because the process will be killed.
return EvalJs(shell(), window_open_script, EXECUTE_SCRIPT_USE_MANUAL_REPLY)
.ExtractBool();
}
// Workers will live throughout the test case unless terminated.
bool CheckCanWorkerFetch(const std::string& worker_name,
const std::string& relative_url) {
GURL worker_url =
embedded_test_server()->GetURL("/workers/worker_common.js");
GURL fetch_url = embedded_test_server()->GetURL(relative_url);
std::string script = base::StringPrintf(
"var workers = workers || {};"
"var worker_name = '%s';"
"workers[worker_name] = workers[worker_name] || new Worker('%s');"
"workers[worker_name].onmessage = evt => {"
" if (evt.data != 'wait')"
" window.domAutomationController.send(evt.data === 200);"
"};"
"workers[worker_name].postMessage(\"eval "
" fetch(new Request('%s'))"
" .then(res => postMessage(res.status))"
" .catch(error => postMessage(error.toString()));"
" 'wait'"
"\");",
worker_name.c_str(), worker_url.spec().c_str(),
fetch_url.spec().c_str());
// The JS call will fail if disallowed because the process will be killed.
return EvalJs(shell(), script, EXECUTE_SCRIPT_USE_MANUAL_REPLY)
.ExtractBool();
}
// Terminate and delete the worker.
bool TerminateWorker(const std::string& worker_name) {
std::string script = base::StringPrintf(
"var workers = workers || {};"
"var worker_name = '%s';"
"if (workers[worker_name]) {"
" workers[worker_name].terminate();"
" delete workers[worker_name];"
" window.domAutomationController.send(true);"
"} else {"
" window.domAutomationController.send(false);"
"}",
worker_name.c_str());
// The JS call will fail if disallowed because the process will be killed.
return EvalJs(shell(), script, EXECUTE_SCRIPT_USE_MANUAL_REPLY)
.ExtractBool();
}
// Called by |embedded_test_server()|.
void MonitorRequest(const net::test_server::HttpRequest& request) {
base::AutoLock lock(last_request_lock_);
last_request_relative_url_ = request.relative_url;
}
std::string last_request_relative_url() const {
base::AutoLock lock(last_request_lock_);
return last_request_relative_url_;
}
private:
mutable base::Lock last_request_lock_;
std::string last_request_relative_url_ GUARDED_BY(last_request_lock_);
DISALLOW_COPY_AND_ASSIGN(NetworkServiceRestartBrowserTest);
};
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
NetworkServiceProcessRecovery) {
if (IsInProcessNetworkService())
return;
mojo::Remote<network::mojom::NetworkContext> network_context(
CreateNetworkContext());
EXPECT_EQ(net::OK, LoadBasicRequest(network_context.get(), GetTestURL()));
EXPECT_TRUE(network_context.is_bound());
EXPECT_TRUE(network_context.is_connected());
// 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 remote to make sure
// the notification has been received.
network_context.FlushForTesting();
EXPECT_TRUE(network_context.is_bound());
EXPECT_FALSE(network_context.is_connected());
// 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.
mojo::Remote<network::mojom::NetworkContext> network_context2(
CreateNetworkContext());
EXPECT_EQ(net::OK, LoadBasicRequest(network_context2.get(), GetTestURL()));
EXPECT_TRUE(network_context2.is_bound());
EXPECT_TRUE(network_context2.is_connected());
}
void IncrementInt(int* i) {
*i = *i + 1;
}
// This test verifies basic functionality of RegisterNetworkServiceCrashHandler
// and UnregisterNetworkServiceCrashHandler.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, CrashHandlers) {
if (IsInProcessNetworkService())
return;
mojo::Remote<network::mojom::NetworkContext> network_context(
CreateNetworkContext());
EXPECT_TRUE(network_context.is_bound());
// Register 2 crash handlers.
int counter1 = 0;
int counter2 = 0;
base::CallbackListSubscription subscription1 =
RegisterNetworkServiceCrashHandler(
base::BindRepeating(&IncrementInt, base::Unretained(&counter1)));
base::CallbackListSubscription subscription2 =
RegisterNetworkServiceCrashHandler(
base::BindRepeating(&IncrementInt, base::Unretained(&counter2)));
// Crash the NetworkService process.
SimulateNetworkServiceCrash();
// |network_context| will receive an error notification, but it's not
// guaranteed to have arrived at this point. Flush the remote to make sure
// the notification has been received.
network_context.FlushForTesting();
EXPECT_TRUE(network_context.is_bound());
EXPECT_FALSE(network_context.is_connected());
// Verify the crash handlers executed.
EXPECT_EQ(1, counter1);
EXPECT_EQ(1, counter2);
// Revive the NetworkService process.
network_context.reset();
network_context.Bind(CreateNetworkContext());
EXPECT_TRUE(network_context.is_bound());
// Unregister one of the handlers.
subscription2 = {};
// Crash the NetworkService process.
SimulateNetworkServiceCrash();
// |network_context| will receive an error notification, but it's not
// guaranteed to have arrived at this point. Flush the remote to make sure
// the notification has been received.
network_context.FlushForTesting();
EXPECT_TRUE(network_context.is_bound());
EXPECT_FALSE(network_context.is_connected());
// Verify only the first crash handler executed.
EXPECT_EQ(2, counter1);
EXPECT_EQ(1, counter2);
}
// Make sure |StoragePartitionImpl::GetNetworkContext()| returns valid interface
// after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
StoragePartitionImplGetNetworkContext) {
if (IsInProcessNetworkService())
return;
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) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter =
partition->url_loader_factory_getter();
auto factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
url_loader_factory_getter.get());
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(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.
factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
url_loader_factory_getter.get());
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
}
// Make sure the factory returned from
// |URLLoaderFactoryGetter::GetNetworkFactory()| continues to work after
// crashes.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
BrowserIOSharedURLLoaderFactory) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
auto factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
partition->url_loader_factory_getter().get());
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(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->url_loader_factory_getter()
->FlushNetworkInterfaceOnIOThreadForTesting();
// |shared_factory| should continue to work.
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
}
// Make sure the factory returned from
// |URLLoaderFactoryGetter::GetNetworkFactory()| doesn't crash if
// it's called after the StoragePartition is deleted.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
BrowserIOSharedFactoryAfterStoragePartitionGone) {
if (IsInProcessNetworkService())
return;
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ShellBrowserContext> browser_context =
std::make_unique<ShellBrowserContext>(true);
auto* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context.get()));
auto factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
partition->url_loader_factory_getter().get());
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
browser_context.reset();
EXPECT_EQ(net::ERR_FAILED,
factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
}
// Make sure basic navigation works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
NavigationURLLoaderBasic) {
if (IsInProcessNetworkService())
return;
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")));
}
// Make sure basic XHR works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, BasicXHR) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_TRUE(CheckCanLoadHttp(shell(), "/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(shell(), "/title2.html"));
EXPECT_EQ(last_request_relative_url(), "/title2.html");
}
// Make sure the factory returned from
// |StoragePartition::GetURLLoaderFactoryForBrowserProcess()| continues to work
// after crashes.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, BrowserUIFactory) {
if (IsInProcessNetworkService())
return;
auto* partition =
BrowserContext::GetDefaultStoragePartition(browser_context());
auto* factory = partition->GetURLLoaderFactoryForBrowserProcess().get();
EXPECT_EQ(net::OK, LoadBasicRequestOnUIThread(factory, GetTestURL()));
SimulateNetworkServiceCrash();
// Flush the interface to make sure the error notification was received.
partition->FlushNetworkInterfaceForTesting();
EXPECT_EQ(net::OK, LoadBasicRequestOnUIThread(factory, GetTestURL()));
}
// Make sure the factory returned from
// |StoragePartition::GetURLLoaderFactoryForBrowserProcess()| doesn't crash if
// it's called after the StoragePartition is deleted.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
BrowserUIFactoryAfterStoragePartitionGone) {
if (IsInProcessNetworkService())
return;
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<ShellBrowserContext> browser_context =
std::make_unique<ShellBrowserContext>(true);
auto* partition =
BrowserContext::GetDefaultStoragePartition(browser_context.get());
scoped_refptr<network::SharedURLLoaderFactory> factory(
partition->GetURLLoaderFactoryForBrowserProcess());
EXPECT_EQ(net::OK, LoadBasicRequestOnUIThread(factory.get(), GetTestURL()));
browser_context.reset();
EXPECT_EQ(net::ERR_FAILED,
LoadBasicRequestOnUIThread(factory.get(), GetTestURL()));
}
// Make sure the factory pending factory returned from
// |StoragePartition::GetURLLoaderFactoryForBrowserProcessIOThread()| can be
// used after crashes.
// Flaky on Windows. https://crbug.com/840127
#if defined(OS_WIN)
#define MAYBE_BrowserIOPendingFactory DISABLED_BrowserIOPendingFactory
#else
#define MAYBE_BrowserIOPendingFactory BrowserIOPendingFactory
#endif
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
MAYBE_BrowserIOPendingFactory) {
if (IsInProcessNetworkService())
return;
auto* partition =
BrowserContext::GetDefaultStoragePartition(browser_context());
auto pending_shared_url_loader_factory =
partition->GetURLLoaderFactoryForBrowserProcessIOThread();
SimulateNetworkServiceCrash();
// Flush the interface to make sure the error notification was received.
partition->FlushNetworkInterfaceForTesting();
static_cast<StoragePartitionImpl*>(partition)
->url_loader_factory_getter()
->FlushNetworkInterfaceOnIOThreadForTesting();
auto factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
std::move(pending_shared_url_loader_factory));
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
}
// Make sure the factory constructed from
// |StoragePartition::GetURLLoaderFactoryForBrowserProcessIOThread()| continues
// to work after crashes.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, BrowserIOFactory) {
if (IsInProcessNetworkService())
return;
auto* partition =
BrowserContext::GetDefaultStoragePartition(browser_context());
auto factory_owner = IOThreadSharedURLLoaderFactoryOwner::Create(
partition->GetURLLoaderFactoryForBrowserProcessIOThread());
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
SimulateNetworkServiceCrash();
// Flush the interface to make sure the error notification was received.
partition->FlushNetworkInterfaceForTesting();
static_cast<StoragePartitionImpl*>(partition)
->url_loader_factory_getter()
->FlushNetworkInterfaceOnIOThreadForTesting();
EXPECT_EQ(net::OK, factory_owner->LoadBasicRequestOnIOThread(GetTestURL()));
}
// Make sure the window from |window.open()| can load XHR after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, WindowOpenXHR) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_TRUE(CheckCanLoadHttpInWindowOpen("/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(CheckCanLoadHttpInWindowOpen("/title2.html"));
EXPECT_EQ(last_request_relative_url(), "/title2.html");
}
// Run tests with PlzDedicatedWorker.
// TODO(https://crbug.com/906991): Merge this test fixture into
// NetworkServiceRestartBrowserTest once PlzDedicatedWorker is enabled by
// default.
class NetworkServiceRestartForWorkerBrowserTest
: public NetworkServiceRestartBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
NetworkServiceRestartForWorkerBrowserTest() {
if (GetParam()) {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kPlzDedicatedWorker);
} else {
scoped_feature_list_.InitAndDisableFeature(
blink::features::kPlzDedicatedWorker);
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(All,
NetworkServiceRestartForWorkerBrowserTest,
::testing::Values(false, true));
// Make sure worker fetch works after crash.
IN_PROC_BROWSER_TEST_P(NetworkServiceRestartForWorkerBrowserTest, WorkerFetch) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_TRUE(CheckCanWorkerFetch("worker1", "/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(CheckCanWorkerFetch("worker1", "/title2.html"));
EXPECT_EQ(last_request_relative_url(), "/title2.html");
}
// Make sure multiple workers are tracked correctly and work after crash.
IN_PROC_BROWSER_TEST_P(NetworkServiceRestartForWorkerBrowserTest,
MultipleWorkerFetch) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
EXPECT_TRUE(CheckCanWorkerFetch("worker1", "/title1.html"));
EXPECT_TRUE(CheckCanWorkerFetch("worker2", "/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();
// Both workers should work after crash.
EXPECT_TRUE(CheckCanWorkerFetch("worker1", "/title2.html"));
EXPECT_TRUE(CheckCanWorkerFetch("worker2", "/title2.html"));
EXPECT_EQ(last_request_relative_url(), "/title2.html");
// Terminate "worker1". "worker2" shouldn't be affected.
EXPECT_TRUE(TerminateWorker("worker1"));
EXPECT_TRUE(CheckCanWorkerFetch("worker2", "/title1.html"));
EXPECT_EQ(last_request_relative_url(), "/title1.html");
// Crash the NetworkService process again. "worker2" should still work.
SimulateNetworkServiceCrash();
partition->FlushNetworkInterfaceForTesting();
main_frame()->FlushNetworkAndNavigationInterfacesForTesting();
EXPECT_TRUE(CheckCanWorkerFetch("worker2", "/title2.html"));
EXPECT_EQ(last_request_relative_url(), "/title2.html");
}
// Make sure fetch from a page controlled by a service worker which doesn't have
// a fetch handler works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
FetchFromServiceWorkerControlledPage_NoFetchHandler) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
ServiceWorkerStatusObserver observer;
ServiceWorkerContextWrapper* service_worker_context =
partition->GetServiceWorkerContext();
service_worker_context->AddObserver(&observer);
// Register a service worker which controls /service_worker/.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('empty.js')"));
// Navigate to a controlled page.
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/service_worker/fetch_from_page.html")));
// Fetch from the controlled page.
const std::string script = "fetch_from_page('/echo');";
EXPECT_EQ("Echo", EvalJs(shell(), script));
// 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();
// Service worker should be stopped when network service crashes.
observer.WaitForStopped();
// Fetch from the controlled page again.
EXPECT_EQ("Echo", EvalJs(shell(), script));
service_worker_context->RemoveObserver(&observer);
}
// Make sure fetch from a page controlled by a service worker which has a fetch
// handler but falls back to the network works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
FetchFromServiceWorkerControlledPage_PassThrough) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
ServiceWorkerStatusObserver observer;
ServiceWorkerContextWrapper* service_worker_context =
partition->GetServiceWorkerContext();
service_worker_context->AddObserver(&observer);
// Register a service worker which controls /service_worker/.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('fetch_event_pass_through.js')"));
// Navigate to a controlled page.
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/service_worker/fetch_from_page.html")));
// Fetch from the controlled page.
const std::string script = "fetch_from_page('/echo');";
EXPECT_EQ("Echo", EvalJs(shell(), script));
// 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();
// Service worker should be stopped when network service crashes.
observer.WaitForStopped();
// Fetch from the controlled page again.
EXPECT_EQ("Echo", EvalJs(shell(), script));
service_worker_context->RemoveObserver(&observer);
}
// Make sure fetch from a page controlled by a service worker which has a fetch
// handler and responds with fetch() works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
FetchFromServiceWorkerControlledPage_RespondWithFetch) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
ServiceWorkerStatusObserver observer;
ServiceWorkerContextWrapper* service_worker_context =
partition->GetServiceWorkerContext();
service_worker_context->AddObserver(&observer);
// Register a service worker which controls /service_worker/.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE",
EvalJs(shell(), "register('fetch_event_respond_with_fetch.js')"));
// Navigate to a controlled page.
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/service_worker/fetch_from_page.html")));
// Fetch from the controlled page.
const std::string script = "fetch_from_page('/echo');";
EXPECT_EQ("Echo", EvalJs(shell(), script));
// 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();
// Service worker should be stopped when network service crashes.
observer.WaitForStopped();
// Fetch from the controlled page again.
EXPECT_EQ("Echo", EvalJs(shell(), script));
service_worker_context->RemoveObserver(&observer);
}
// Make sure fetch from service worker context works after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, ServiceWorkerFetch) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
ServiceWorkerStatusObserver observer;
ServiceWorkerContextWrapper* service_worker_context =
partition->GetServiceWorkerContext();
service_worker_context->AddObserver(&observer);
const GURL page_url = embedded_test_server()->GetURL(
"/service_worker/fetch_from_service_worker.html");
const GURL fetch_url = embedded_test_server()->GetURL("/echo");
// Navigate to the page and register a service worker.
EXPECT_TRUE(NavigateToURL(shell(), page_url));
EXPECT_EQ("ready", EvalJs(shell(), "setup();"));
// Fetch from the service worker.
const std::string script =
"fetch_from_service_worker('" + fetch_url.spec() + "');";
EXPECT_EQ("Echo", EvalJs(shell(), script));
// 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();
// Service worker should be stopped when network service crashes.
observer.WaitForStopped();
// Fetch from the service worker again.
EXPECT_EQ("Echo", EvalJs(shell(), script));
service_worker_context->RemoveObserver(&observer);
}
// TODO(crbug.com/154571): Shared workers are not available on Android.
#if defined(OS_ANDROID)
#define MAYBE_SharedWorker DISABLED_SharedWorker
#else
#define MAYBE_SharedWorker SharedWorker
#endif
// Make sure shared workers terminate after crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, MAYBE_SharedWorker) {
if (IsInProcessNetworkService())
return;
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
InjectTestSharedWorkerService(partition);
const GURL page_url =
embedded_test_server()->GetURL("/workers/fetch_from_shared_worker.html");
const GURL fetch_url = embedded_test_server()->GetURL("/echo");
// Navigate to the page and prepare a shared worker.
EXPECT_TRUE(NavigateToURL(shell(), page_url));
// Fetch from the shared worker to ensure it has started.
const std::string script =
"fetch_from_shared_worker('" + fetch_url.spec() + "');";
EXPECT_EQ("Echo", EvalJs(shell(), script));
// There should be one worker host. We will later wait for it to terminate.
TestSharedWorkerServiceImpl* service =
static_cast<TestSharedWorkerServiceImpl*>(
partition->GetSharedWorkerService());
EXPECT_EQ(1u, service->worker_hosts_.size());
base::RunLoop loop;
service->SetWorkerTerminationCallback(loop.QuitClosure());
// Crash the NetworkService process.
SimulateNetworkServiceCrash();
// Wait for the worker to detect the crash and self-terminate.
loop.Run();
EXPECT_TRUE(service->worker_hosts_.empty());
}
// Make sure that kSSLKeyLogFileHistogram is correctly recorded when the
// network service instance is started and the SSLKEYLOGFILE env var is set or
// the "--ssl-key-log-file" arg is set.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, SSLKeyLogFileMetrics) {
if (IsInProcessNetworkService())
return;
// Actions on temporary files are blocking.
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
base::FilePath log_file_path;
base::CreateTemporaryFile(&log_file_path);
#if defined(OS_WIN)
// On Windows, FilePath::value() returns std::wstring, so convert.
std::string log_file_path_str = base::WideToUTF8(log_file_path.value());
#else
std::string log_file_path_str = log_file_path.value();
#endif
// Test that env var causes the histogram to be recorded.
{
base::test::ScopedEnvironmentVariableOverride scoped_env("SSLKEYLOGFILE",
log_file_path_str);
base::HistogramTester histograms;
// Restart network service to cause SSLKeyLogger to be re-initialized.
SimulateNetworkServiceCrash();
histograms.ExpectBucketCount(kSSLKeyLogFileHistogram,
SSLKeyLogFileAction::kLogFileEnabled, 1);
histograms.ExpectBucketCount(kSSLKeyLogFileHistogram,
SSLKeyLogFileAction::kEnvVarFound, 1);
}
// Test that the command-line switch causes the histogram to be recorded.
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitchPath(
"ssl-key-log-file", log_file_path);
base::HistogramTester histograms;
// Restart network service to cause SSLKeyLogger to be re-initialized.
SimulateNetworkServiceCrash();
histograms.ExpectBucketCount(kSSLKeyLogFileHistogram,
SSLKeyLogFileAction::kLogFileEnabled, 1);
histograms.ExpectBucketCount(kSSLKeyLogFileHistogram,
SSLKeyLogFileAction::kSwitchFound, 1);
}
}
// Make sure cookie access doesn't hang or fail after a network process crash.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, Cookies) {
if (IsInProcessNetworkService())
return;
auto* web_contents = shell()->web_contents();
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
EXPECT_TRUE(ExecJs(web_contents, "document.cookie = 'foo=bar';"));
EXPECT_EQ("foo=bar", EvalJs(web_contents, "document.cookie;"));
SimulateNetworkServiceCrash();
// content_shell uses in-memory cookie database, so the value saved earlier
// won't persist across crashes. What matters is that new access works.
EXPECT_TRUE(ExecJs(web_contents, "document.cookie = 'foo=bar';"));
// This will hang without the fix.
EXPECT_EQ("foo=bar", EvalJs(web_contents, "document.cookie;"));
}
#if BUILDFLAG(ENABLE_PLUGINS)
// Make sure that "trusted" plugins continue to be able to issue requests that
// are cross-origin (wrt the host frame) after a network process crash:
// - html frame: main-frame.com/title1.html
// \-- plugin document: cross.origin.com/.../js.txt (`plugin_document_url`)
// \-- request from plugin: cross.origin.com/.../js.txt
// This mimics the behavior of PDFs, which only issue requests for the plugin
// document (e.q. network::ResourceRequest::request_initiator is same-origin wrt
// ResourceRequest::url).
//
// This primarily verifies that OnNetworkServiceCrashRestorePluginExceptions in
// render_process_host_impl.cc refreshes AddAllowedRequestInitiatorForPlugin
// data after a NetworkService crash.
//
// See also https://crbug.com/874515 and https://crbug.com/846339.
//
// TODO(lukasza, kmoon): https://crbug.com/702993: Remove this test once PDF
// support doesn't depend on PPAPI anymore.
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest, Plugin) {
if (IsInProcessNetworkService())
return;
auto* web_contents = shell()->web_contents();
ASSERT_TRUE(NavigateToURL(
web_contents,
embedded_test_server()->GetURL("main.frame.com", "/title1.html")));
// Load cross-origin document into the test plugin (see
// ppapi::RegisterCorbTestPlugin).
//
// The document has to be a MIME type that CORB will allow (such as
// javascript) - it cannot be a pdf or json, because these would be blocked by
// CORB (the real PDF plugin works because the plugin is hosted in a Chrome
// Extension where CORB is turned off).
GURL plugin_document_url = embedded_test_server()->GetURL(
"cross.origin.com", "/site_isolation/js.txt");
const char kLoadingScriptTemplate[] = R"(
var obj = document.createElement('object');
obj.id = 'plugin';
obj.data = $1;
obj.type = 'application/x-fake-pdf-for-testing';
obj.width = 400;
obj.height = 400;
document.body.appendChild(obj);
)";
EXPECT_FALSE(
web_contents->GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith(
url::Origin::Create(plugin_document_url)));
ASSERT_TRUE(ExecJs(web_contents,
JsReplace(kLoadingScriptTemplate, plugin_document_url)));
// Ask the plugin to re-request the document URL (similarly to what the PDF
// plugin does to get chunks of linearized PDFs).
const char kFetchScriptTemplate[] = R"(
new Promise(function (resolve, reject) {
var obj = document.getElementById('plugin');
function callback(event) {
// Ignore plugin messages unrelated to requestUrl.
if (!event.data.startsWith('requestUrl: '))
return;
obj.removeEventListener('message', callback);
resolve('msg-from-plugin: ' + event.data);
};
obj.addEventListener('message', callback);
obj.postMessage('requestUrl: ' + $1);
});
)";
std::string fetch_script =
JsReplace(kFetchScriptTemplate, plugin_document_url);
ASSERT_EQ(
"msg-from-plugin: requestUrl: RESPONSE BODY: "
"var j = 0; document.write(j);\n",
EvalJs(web_contents, fetch_script));
// Crash the Network Service process and wait until host frame's
// URLLoaderFactory has been refreshed.
SimulateNetworkServiceCrash();
main_frame()->FlushNetworkAndNavigationInterfacesForTesting();
// Try the fetch again - it should still work (i.e. the mechanism for relaxing
// request_initiator_origin_lock enforcement should be resilient to network
// process crashes).
ASSERT_EQ(
"msg-from-plugin: requestUrl: RESPONSE BODY: "
"var j = 0; document.write(j);\n",
EvalJs(web_contents, fetch_script));
}
#endif
// TODO(crbug.com/901026): Fix deadlock on process startup on Android.
#if defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
DISABLED_SyncCallDuringRestart) {
if (IsInProcessNetworkService())
return;
base::RunLoop run_loop;
mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
content::GetNetworkService()->BindTestInterface(
network_service_test.BindNewPipeAndPassReceiver());
// Crash the network service, but do not wait for full startup.
network_service_test.set_disconnect_handler(run_loop.QuitClosure());
network_service_test->SimulateCrash();
run_loop.Run();
network_service_test.reset();
content::GetNetworkService()->BindTestInterface(
network_service_test.BindNewPipeAndPassReceiver());
// Sync call should be fine, even though network process is still starting up.
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
network_service_test->AddRules({});
}
#endif
// Tests handling of a NetworkService crash that happens after a navigation
// triggers sending a Commit IPC to the renderer process, but before a DidCommit
// IPC from the renderer process is handled. See also
// https://crbug.com/1056949#c75.
//
// TODO(lukasza): https://crbug.com/1129592: Flaky on Android. No flakiness
// observed whatsoever on Windows, Linux or CrOS.
#if defined(OS_ANDROID)
#define MAYBE_BetweenCommitNavigationAndDidCommit \
DISABLED_BetweenCommitNavigationAndDidCommit
#else
#define MAYBE_BetweenCommitNavigationAndDidCommit \
BetweenCommitNavigationAndDidCommit
#endif
IN_PROC_BROWSER_TEST_F(NetworkServiceRestartBrowserTest,
MAYBE_BetweenCommitNavigationAndDidCommit) {
if (IsInProcessNetworkService())
return;
GURL initial_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
// Crash the NetworkService while CommitNavigation IPC is in-flight and before
// DidCommit IPC is handled. This tests how RenderFrameHostImpl recreates the
// URLLoaderFactory after NetworkService crash. In particular,
// RenderFrameHostImpl::UpdateSubresourceLoaderFactories needs to use the
// |request_initiator_origin_lock| associated with the in-flight IPC (because
// the |RFHI::last_committed_origin_| won't be updated until DidCommit IPC is
// handled).
auto pre_did_commit_lambda = [&](RenderFrameHost* frame) {
// 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.
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
partition->FlushNetworkInterfaceForTesting();
};
CommitMessageDelayer::DidCommitCallback pre_did_commit_callback =
base::BindLambdaForTesting(std::move(pre_did_commit_lambda));
GURL final_page_url(
embedded_test_server()->GetURL("bar.com", "/title2.html"));
CommitMessageDelayer did_commit_delayer(shell()->web_contents(),
final_page_url,
std::move(pre_did_commit_callback));
ASSERT_TRUE(ExecJs(shell(), JsReplace("location = $1", final_page_url)));
did_commit_delayer.Wait();
// Test if subresources requests work fine (e.g. if |request_initiator|
// matches |request_initiator_origin_lock|).
GURL final_resource_url(
embedded_test_server()->GetURL("bar.com", "/site_isolation/json.txt"));
EXPECT_EQ(
"{ \"name\" : \"chromium\" }\n",
EvalJs(shell(), JsReplace("fetch($1).then(response => response.text())",
final_resource_url)));
}
} // namespace content