blob: ef4c915c5690954d04ee802e99e8aea208ea2a97 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/origin.h"
// This file has tests involving render process selection for service workers.
namespace content {
// An observer that waits for the service worker to be running.
class WorkerRunningStatusObserver : public ServiceWorkerContextObserver {
public:
explicit WorkerRunningStatusObserver(ServiceWorkerContext* context) {
scoped_context_observation_.Observe(context);
}
WorkerRunningStatusObserver(const WorkerRunningStatusObserver&) = delete;
WorkerRunningStatusObserver& operator=(const WorkerRunningStatusObserver&) =
delete;
~WorkerRunningStatusObserver() override = default;
int64_t version_id() { return version_id_; }
void WaitUntilRunning() {
if (version_id_ == blink::mojom::kInvalidServiceWorkerVersionId) {
run_loop_.Run();
}
}
void OnVersionStartedRunning(
int64_t version_id,
const ServiceWorkerRunningInfo& running_info) override {
version_id_ = version_id;
if (run_loop_.running()) {
run_loop_.Quit();
}
}
private:
base::RunLoop run_loop_;
base::ScopedObservation<ServiceWorkerContext, ServiceWorkerContextObserver>
scoped_context_observation_{this};
int64_t version_id_ = blink::mojom::kInvalidServiceWorkerVersionId;
};
class ServiceWorkerProcessBrowserTest
: public ContentBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
ServiceWorkerProcessBrowserTest() = default;
~ServiceWorkerProcessBrowserTest() override = default;
ServiceWorkerProcessBrowserTest(const ServiceWorkerProcessBrowserTest&) =
delete;
ServiceWorkerProcessBrowserTest& operator=(
const ServiceWorkerProcessBrowserTest&) = delete;
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
StoragePartition* partition = shell()
->web_contents()
->GetBrowserContext()
->GetDefaultStoragePartition();
wrapper_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
if (SitePerProcess()) {
command_line->AppendSwitch(switches::kSitePerProcess);
} else {
command_line->RemoveSwitch(switches::kSitePerProcess);
command_line->AppendSwitch(switches::kDisableSiteIsolation);
}
}
protected:
bool SitePerProcess() const { return GetParam(); }
// Registers a service worker and then tears down the process it used, for a
// clean slate going forward.
void RegisterServiceWorker() {
// Load a page that registers a service worker.
Shell* start_shell = CreateBrowser();
ASSERT_TRUE(NavigateToURL(
start_shell, embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
ASSERT_EQ("DONE",
EvalJs(start_shell, "register('fetch_event_pass_through.js');"));
auto* host = RenderProcessHost::FromID(GetServiceWorkerProcessId());
ASSERT_TRUE(host);
RenderProcessHostWatcher exit_watcher(
host, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
// Tear down the page.
start_shell->Close();
// Stop the service worker. The process should exit.
base::RunLoop loop;
wrapper()->StopAllServiceWorkers(loop.QuitClosure());
loop.Run();
exit_watcher.Wait();
}
// Returns the number of running service workers.
size_t GetRunningServiceWorkerCount() {
return wrapper()->GetRunningServiceWorkerInfos().size();
}
// Returns the process id of the running service worker. There must be exactly
// one service worker running.
int GetServiceWorkerProcessId() {
const base::flat_map<int64_t, ServiceWorkerRunningInfo>& infos =
wrapper()->GetRunningServiceWorkerInfos();
DCHECK_EQ(infos.size(), 1u);
const ServiceWorkerRunningInfo& info = infos.begin()->second;
return info.render_process_id;
}
ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
ServiceWorkerContext* public_context() { return wrapper(); }
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* current_frame_host() {
return web_contents()->GetPrimaryFrameTree().root()->current_frame_host();
}
private:
base::test::ScopedFeatureList feature_list_;
scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
};
// Tests that a service worker started due to a navigation shares the same
// process as the navigation.
// Flaky on Android; see https://crbug.com/1320972.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ServiceWorkerAndPageShareProcess \
DISABLED_ServiceWorkerAndPageShareProcess
#else
#define MAYBE_ServiceWorkerAndPageShareProcess ServiceWorkerAndPageShareProcess
#endif
IN_PROC_BROWSER_TEST_P(ServiceWorkerProcessBrowserTest,
MAYBE_ServiceWorkerAndPageShareProcess) {
// Register the service worker.
RegisterServiceWorker();
// Navigate to a page in the service worker's scope.
WorkerRunningStatusObserver observer(public_context());
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
observer.WaitUntilRunning();
// The page and service worker should be in the same process.
int page_process_id = current_frame_host()->GetProcess()->GetID();
EXPECT_NE(page_process_id, ChildProcessHost::kInvalidUniqueID);
ASSERT_EQ(GetRunningServiceWorkerCount(), 1u);
int worker_process_id = GetServiceWorkerProcessId();
EXPECT_EQ(page_process_id, worker_process_id);
}
// Tests whether a service worker and navigation share the same process in the
// special case where the service worker starts before the navigation starts,
// and the navigation transitions out of a page with no site URL. This special
// case happens in real life when doing a search from the omnibox while on the
// Android native NTP page: the service worker starts first due to the
// navigation hint from the omnibox, and the native page has no site URL. See
// https://crbug.com/1012143.
IN_PROC_BROWSER_TEST_P(ServiceWorkerProcessBrowserTest,
NavigateFromUnassignedSiteInstance) {
// Set up an empty page scheme whose URLs will have no site assigned. This
// requires setting it as an empty document scheme.
url::ScopedSchemeRegistryForTests scheme_registry;
url::AddEmptyDocumentScheme("siteless");
GURL empty_site_url = GURL("siteless://test");
EXPECT_FALSE(SiteInstance::ShouldAssignSiteForURL(empty_site_url));
// Register the service worker.
RegisterServiceWorker();
// Navigate to the empty site instance page.
ASSERT_TRUE(NavigateToURL(shell(), empty_site_url));
EXPECT_EQ(web_contents()->GetLastCommittedURL(), empty_site_url);
scoped_refptr<SiteInstanceImpl> site_instance =
web_contents()->GetPrimaryMainFrame()->GetSiteInstance();
EXPECT_EQ(GURL(), site_instance->GetSiteURL());
int page_process_id = current_frame_host()->GetProcess()->GetID();
EXPECT_NE(page_process_id, ChildProcessHost::kInvalidUniqueID);
// Start the service worker.
base::RunLoop loop;
GURL scope = embedded_test_server()->GetURL("/service_worker/");
int worker_process_id;
wrapper()->ServiceWorkerContextWrapper::StartWorkerForScope(
scope, blink::StorageKey::CreateFirstParty(url::Origin::Create(scope)),
base::BindLambdaForTesting(
[&](int64_t version_id, int process_id, int thread_id) {
worker_process_id = process_id;
loop.Quit();
}),
base::BindLambdaForTesting(
[&loop](blink::ServiceWorkerStatusCode status_code) {
ASSERT_FALSE(true) << "start worker failed";
loop.Quit();
}));
loop.Run();
// The page and service worker are in different processes. (This is not
// necessarily the desired behavior, but the current one of the
// implementation.)
EXPECT_NE(page_process_id, worker_process_id);
// Navigate to a page in the service worker's scope. It should be in the
// same process as the original page.
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/service_worker/empty.html")));
EXPECT_EQ(page_process_id, current_frame_host()->GetProcess()->GetID());
}
// Toggle Site Isolation.
INSTANTIATE_TEST_SUITE_P(All, ServiceWorkerProcessBrowserTest, testing::Bool());
} // namespace content