blob: 17e96eaeeeeb1d96fd02e0908918040ba04df88a [file] [log] [blame]
// Copyright 2014 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 "content/browser/service_worker/service_worker_provider_host.h"
#include <memory>
#include <utility>
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_dispatcher_host.h"
#include "content/browser/service_worker/service_worker_register_job.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/url_schemes.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/origin_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "mojo/core/embedder/embedder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
namespace content {
namespace {
const char kServiceWorkerScheme[] = "i-can-use-service-worker";
class ServiceWorkerTestContentClient : public TestContentClient {
public:
void AddAdditionalSchemes(Schemes* schemes) override {
schemes->service_worker_schemes.push_back(kServiceWorkerScheme);
}
};
class ServiceWorkerTestContentBrowserClient : public TestContentBrowserClient {
public:
struct AllowServiceWorkerCallLog {
AllowServiceWorkerCallLog(const GURL& scope, const GURL& first_party)
: scope(scope), first_party(first_party) {}
const GURL scope;
const GURL first_party;
};
ServiceWorkerTestContentBrowserClient() {}
bool AllowServiceWorker(
const GURL& scope,
const GURL& first_party,
content::ResourceContext* context,
base::RepeatingCallback<WebContents*()> wc_getter) override {
logs_.emplace_back(scope, first_party);
return false;
}
const std::vector<AllowServiceWorkerCallLog>& logs() const { return logs_; }
private:
std::vector<AllowServiceWorkerCallLog> logs_;
};
} // namespace
class ServiceWorkerProviderHostTest : public testing::TestWithParam<bool> {
protected:
ServiceWorkerProviderHostTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
next_renderer_provided_id_(1) {
SetContentClient(&test_content_client_);
}
~ServiceWorkerProviderHostTest() override {}
void SetUp() override {
if (IsServiceWorkerServicificationEnabled()) {
scoped_feature_list_.InitAndEnableFeature(
blink::features::kServiceWorkerServicification);
} else {
scoped_feature_list_.InitAndDisableFeature(
blink::features::kServiceWorkerServicification);
}
old_content_browser_client_ =
SetBrowserClientForTesting(&test_content_browser_client_);
ResetSchemesAndOriginsWhitelist();
mojo::core::SetDefaultProcessErrorCallback(base::Bind(
&ServiceWorkerProviderHostTest::OnMojoError, base::Unretained(this)));
helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
context_ = helper_->context();
script_url_ = GURL("https://www.example.com/service_worker.js");
blink::mojom::ServiceWorkerRegistrationOptions options1;
options1.scope = GURL("https://www.example.com/");
registration1_ =
new ServiceWorkerRegistration(options1, 1L, context_->AsWeakPtr());
blink::mojom::ServiceWorkerRegistrationOptions options2;
options2.scope = GURL("https://www.example.com/example");
registration2_ =
new ServiceWorkerRegistration(options2, 2L, context_->AsWeakPtr());
blink::mojom::ServiceWorkerRegistrationOptions options3;
options3.scope = GURL("https://other.example.com/");
registration3_ =
new ServiceWorkerRegistration(options3, 3L, context_->AsWeakPtr());
}
void TearDown() override {
registration1_ = nullptr;
registration2_ = nullptr;
registration3_ = nullptr;
helper_.reset();
SetBrowserClientForTesting(old_content_browser_client_);
// Reset cached security schemes so we don't affect other tests.
ResetSchemesAndOriginsWhitelist();
mojo::core::SetDefaultProcessErrorCallback(
mojo::core::ProcessErrorCallback());
}
ServiceWorkerRemoteProviderEndpoint PrepareServiceWorkerProviderHost(
const GURL& document_url) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
GURL site_for_cookies = document_url;
CreateProviderHostInternal(document_url, site_for_cookies,
&remote_endpoint);
return remote_endpoint;
}
ServiceWorkerRemoteProviderEndpoint
PrepareServiceWorkerProviderHostWithSiteForCookies(
const GURL& document_url,
const GURL& site_for_cookies) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
CreateProviderHostInternal(document_url, site_for_cookies,
&remote_endpoint);
return remote_endpoint;
}
ServiceWorkerProviderHost* CreateProviderHost(const GURL& document_url) {
GURL site_for_cookies = document_url;
remote_endpoints_.emplace_back();
return CreateProviderHostInternal(document_url, site_for_cookies,
&remote_endpoints_.back());
}
ServiceWorkerProviderHost* CreateProviderHostWithInsecureParentFrame(
const GURL& document_url) {
remote_endpoints_.emplace_back();
std::unique_ptr<ServiceWorkerProviderHost> host =
CreateProviderHostForWindow(
helper_->mock_render_process_id(), next_renderer_provided_id_++,
false /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoints_.back());
ServiceWorkerProviderHost* host_raw = host.get();
host->UpdateUrls(document_url, document_url);
context_->AddProviderHost(std::move(host));
return host_raw;
}
void FinishNavigation(ServiceWorkerProviderHost* host,
blink::mojom::ServiceWorkerProviderHostInfoPtr info) {
// In production code, the loader/request handler does this.
const GURL url("https://www.example.com/page");
host->UpdateUrls(url, url);
// In production code, the OnProviderCreated IPC is received which
// does this.
host->CompleteNavigationInitialized(helper_->mock_render_process_id(),
std::move(info));
}
blink::mojom::ServiceWorkerErrorType Register(
blink::mojom::ServiceWorkerContainerHost* container_host,
GURL scope,
GURL worker_url) {
blink::mojom::ServiceWorkerErrorType error =
blink::mojom::ServiceWorkerErrorType::kUnknown;
auto options = blink::mojom::ServiceWorkerRegistrationOptions::New();
options->scope = scope;
container_host->Register(
worker_url, std::move(options),
base::BindOnce([](blink::mojom::ServiceWorkerErrorType* out_error,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg,
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
registration) { *out_error = error; },
&error));
base::RunLoop().RunUntilIdle();
return error;
}
blink::mojom::ServiceWorkerErrorType GetRegistration(
blink::mojom::ServiceWorkerContainerHost* container_host,
GURL document_url,
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr* out_info =
nullptr) {
blink::mojom::ServiceWorkerErrorType error =
blink::mojom::ServiceWorkerErrorType::kUnknown;
container_host->GetRegistration(
document_url,
base::BindOnce(
[](blink::mojom::ServiceWorkerErrorType* out_error,
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr* out_info,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg,
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
registration) {
*out_error = error;
if (out_info)
*out_info = std::move(registration);
},
&error, out_info));
base::RunLoop().RunUntilIdle();
return error;
}
blink::mojom::ServiceWorkerErrorType GetRegistrations(
blink::mojom::ServiceWorkerContainerHost* container_host) {
blink::mojom::ServiceWorkerErrorType error =
blink::mojom::ServiceWorkerErrorType::kUnknown;
container_host->GetRegistrations(base::BindOnce(
[](blink::mojom::ServiceWorkerErrorType* out_error,
blink::mojom::ServiceWorkerErrorType error,
const base::Optional<std::string>& error_msg,
base::Optional<std::vector<
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr>> infos) {
*out_error = error;
},
&error));
base::RunLoop().RunUntilIdle();
return error;
}
void OnMojoError(const std::string& error) { bad_messages_.push_back(error); }
bool CanFindClientProviderHost(ServiceWorkerProviderHost* host) {
for (std::unique_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
context_->GetClientProviderHostIterator(
host->url().GetOrigin(), false /* include_reserved_clients */);
!it->IsAtEnd(); it->Advance()) {
if (host == it->GetProviderHost())
return true;
}
return false;
}
bool IsServiceWorkerServicificationEnabled() { return GetParam(); }
void ExpectUpdateIsScheduled(ServiceWorkerVersion* version) {
EXPECT_TRUE(version->is_update_scheduled_);
EXPECT_TRUE(version->update_timer_.IsRunning());
}
void ExpectUpdateIsNotScheduled(ServiceWorkerVersion* version) {
EXPECT_FALSE(version->is_update_scheduled_);
EXPECT_FALSE(version->update_timer_.IsRunning());
}
bool HasVersionToUpdate(ServiceWorkerProviderHost* host) {
return !host->versions_to_update_.empty();
}
// |scoped_feature_list_| must be before |thread_bundle_|, since
// the thread bundle's destruction causes service worker-related
// objects to destruct, whose destructors need to know whether servicification
// is enabled.
base::test::ScopedFeatureList scoped_feature_list_;
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
ServiceWorkerContextCore* context_;
scoped_refptr<ServiceWorkerRegistration> registration1_;
scoped_refptr<ServiceWorkerRegistration> registration2_;
scoped_refptr<ServiceWorkerRegistration> registration3_;
GURL script_url_;
ServiceWorkerTestContentClient test_content_client_;
TestContentBrowserClient test_content_browser_client_;
ContentBrowserClient* old_content_browser_client_;
int next_renderer_provided_id_;
std::vector<ServiceWorkerRemoteProviderEndpoint> remote_endpoints_;
std::vector<std::string> bad_messages_;
private:
ServiceWorkerProviderHost* CreateProviderHostInternal(
const GURL& document_url,
const GURL& site_for_cookies,
ServiceWorkerRemoteProviderEndpoint* remote_endpoint) {
base::WeakPtr<ServiceWorkerProviderHost> host =
ServiceWorkerProviderHost::PreCreateNavigationHost(
helper_->context()->AsWeakPtr(), true, base::NullCallback());
blink::mojom::ServiceWorkerProviderHostInfoPtr info =
CreateProviderHostInfoForWindow(host->provider_id(), 1 /* route_id */);
remote_endpoint->BindWithProviderHostInfo(&info);
host->CompleteNavigationInitialized(helper_->mock_render_process_id(),
std::move(info));
host->UpdateUrls(document_url, site_for_cookies);
return host.get();
}
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHostTest);
};
TEST_P(ServiceWorkerProviderHostTest, MatchRegistration) {
ServiceWorkerProviderHost* provider_host1 =
CreateProviderHost(GURL("https://www.example.com/example1.html"));
// Match registration should return the longest matching one.
ASSERT_EQ(registration2_, provider_host1->MatchRegistration());
provider_host1->RemoveMatchingRegistration(registration2_.get());
ASSERT_EQ(registration1_, provider_host1->MatchRegistration());
// Should return nullptr after removing all matching registrations.
provider_host1->RemoveMatchingRegistration(registration1_.get());
ASSERT_EQ(nullptr, provider_host1->MatchRegistration());
// SetDocumentUrl sets all of matching registrations
provider_host1->UpdateUrls(GURL("https://www.example.com/example1"),
GURL("https://www.example.com/example1"));
ASSERT_EQ(registration2_, provider_host1->MatchRegistration());
provider_host1->RemoveMatchingRegistration(registration2_.get());
ASSERT_EQ(registration1_, provider_host1->MatchRegistration());
// SetDocumentUrl with another origin also updates matching registrations
provider_host1->UpdateUrls(GURL("https://other.example.com/example"),
GURL("https://other.example.com/example"));
ASSERT_EQ(registration3_, provider_host1->MatchRegistration());
provider_host1->RemoveMatchingRegistration(registration3_.get());
ASSERT_EQ(nullptr, provider_host1->MatchRegistration());
}
TEST_P(ServiceWorkerProviderHostTest, ContextSecurity) {
ServiceWorkerProviderHost* provider_host_secure_parent =
CreateProviderHost(GURL("https://www.example.com/example1.html"));
ServiceWorkerProviderHost* provider_host_insecure_parent =
CreateProviderHostWithInsecureParentFrame(
GURL("https://www.example.com/example1.html"));
// Insecure document URL.
provider_host_secure_parent->UpdateUrls(GURL("http://host"),
GURL("http://host"));
EXPECT_FALSE(provider_host_secure_parent->IsContextSecureForServiceWorker());
// Insecure parent frame.
provider_host_insecure_parent->UpdateUrls(GURL("https://host"),
GURL("https://host"));
EXPECT_FALSE(
provider_host_insecure_parent->IsContextSecureForServiceWorker());
// Secure URL and parent frame.
provider_host_secure_parent->UpdateUrls(GURL("https://host"),
GURL("https://host"));
EXPECT_TRUE(provider_host_secure_parent->IsContextSecureForServiceWorker());
// Exceptional service worker scheme.
GURL url(std::string(kServiceWorkerScheme) + "://host");
EXPECT_TRUE(url.is_valid());
EXPECT_FALSE(IsOriginSecure(url));
EXPECT_TRUE(OriginCanAccessServiceWorkers(url));
provider_host_secure_parent->UpdateUrls(url, url);
EXPECT_TRUE(provider_host_secure_parent->IsContextSecureForServiceWorker());
// Exceptional service worker scheme with insecure parent frame.
provider_host_insecure_parent->UpdateUrls(url, url);
EXPECT_FALSE(
provider_host_insecure_parent->IsContextSecureForServiceWorker());
}
TEST_P(ServiceWorkerProviderHostTest, UpdateUrls_SameOriginRedirect) {
const GURL url1("https://origin1.example.com/page1.html");
const GURL url2("https://origin1.example.com/page2.html");
ServiceWorkerProviderHost* host = CreateProviderHost(url1);
const std::string uuid1 = host->client_uuid();
EXPECT_EQ(url1, host->url());
EXPECT_EQ(url1, host->site_for_cookies());
host->UpdateUrls(url2, url2);
EXPECT_EQ(url2, host->url());
EXPECT_EQ(url2, host->site_for_cookies());
EXPECT_EQ(uuid1, host->client_uuid());
EXPECT_EQ(host, context_->GetProviderHostByClientID(host->client_uuid()));
}
TEST_P(ServiceWorkerProviderHostTest, UpdateUrls_CrossOriginRedirect) {
const GURL url1("https://origin1.example.com/page1.html");
const GURL url2("https://origin2.example.com/page2.html");
ServiceWorkerProviderHost* host = CreateProviderHost(url1);
const std::string uuid1 = host->client_uuid();
EXPECT_EQ(url1, host->url());
EXPECT_EQ(url1, host->site_for_cookies());
host->UpdateUrls(url2, url2);
EXPECT_EQ(url2, host->url());
EXPECT_EQ(url2, host->site_for_cookies());
EXPECT_NE(uuid1, host->client_uuid());
EXPECT_FALSE(context_->GetProviderHostByClientID(uuid1));
EXPECT_EQ(host, context_->GetProviderHostByClientID(host->client_uuid()));
}
class MockServiceWorkerRegistration : public ServiceWorkerRegistration {
public:
MockServiceWorkerRegistration(
const blink::mojom::ServiceWorkerRegistrationOptions& options,
int64_t registration_id,
base::WeakPtr<ServiceWorkerContextCore> context)
: ServiceWorkerRegistration(options, registration_id, context) {}
void AddListener(ServiceWorkerRegistration::Listener* listener) override {
listeners_.insert(listener);
}
void RemoveListener(ServiceWorkerRegistration::Listener* listener) override {
listeners_.erase(listener);
}
const std::set<ServiceWorkerRegistration::Listener*>& listeners() {
return listeners_;
}
protected:
~MockServiceWorkerRegistration() override{};
private:
std::set<ServiceWorkerRegistration::Listener*> listeners_;
};
TEST_P(ServiceWorkerProviderHostTest, RemoveProvider) {
// Create a provider host connected with the renderer process.
ServiceWorkerProviderHost* provider_host =
CreateProviderHost(GURL("https://www.example.com/example1.html"));
int process_id = provider_host->process_id();
int provider_id = provider_host->provider_id();
EXPECT_TRUE(context_->GetProviderHost(process_id, provider_id));
// Disconnect the mojo pipe from the renderer side.
ASSERT_TRUE(remote_endpoints_.back().host_ptr()->is_bound());
remote_endpoints_.back().host_ptr()->reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->GetProviderHost(process_id, provider_id));
}
class MockServiceWorkerContainer : public blink::mojom::ServiceWorkerContainer {
public:
explicit MockServiceWorkerContainer(
blink::mojom::ServiceWorkerContainerAssociatedRequest request)
: binding_(this, std::move(request)) {}
~MockServiceWorkerContainer() override = default;
void SetController(
blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
const std::vector<blink::mojom::WebFeature>& used_features,
bool should_notify_controllerchange) override {
was_set_controller_called_ = true;
}
void PostMessageToClient(blink::mojom::ServiceWorkerObjectInfoPtr controller,
blink::TransferableMessage message) override {}
void CountFeature(blink::mojom::WebFeature feature) override {}
bool was_set_controller_called() const { return was_set_controller_called_; }
private:
bool was_set_controller_called_ = false;
mojo::AssociatedBinding<blink::mojom::ServiceWorkerContainer> binding_;
};
TEST_P(ServiceWorkerProviderHostTest, Controller) {
// Create a host.
base::WeakPtr<ServiceWorkerProviderHost> host =
ServiceWorkerProviderHost::PreCreateNavigationHost(
helper_->context()->AsWeakPtr(), true /* are_ancestors_secure */,
base::NullCallback());
blink::mojom::ServiceWorkerProviderHostInfoPtr info =
CreateProviderHostInfoForWindow(host->provider_id(), 1 /* route_id */);
remote_endpoints_.emplace_back();
remote_endpoints_.back().BindWithProviderHostInfo(&info);
auto container = std::make_unique<MockServiceWorkerContainer>(
std::move(*remote_endpoints_.back().client_request()));
// Create an active version and then start the navigation.
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration1_->SetActiveVersion(version);
// Finish the navigation.
FinishNavigation(host.get(), std::move(info));
host->SetControllerRegistration(registration1_,
false /* notify_controllerchange */);
base::RunLoop().RunUntilIdle();
// The page should be controlled since there was an active version at the
// time navigation started. The SetController IPC should have been sent.
EXPECT_TRUE(host->controller());
EXPECT_TRUE(container->was_set_controller_called());
EXPECT_EQ(registration1_.get(), host->MatchRegistration());
}
TEST_P(ServiceWorkerProviderHostTest, UncontrolledWithMatchingRegistration) {
// Create a host.
base::WeakPtr<ServiceWorkerProviderHost> host =
ServiceWorkerProviderHost::PreCreateNavigationHost(
helper_->context()->AsWeakPtr(), true /* are_ancestors_secure */,
base::NullCallback());
blink::mojom::ServiceWorkerProviderHostInfoPtr info =
CreateProviderHostInfoForWindow(host->provider_id(), 1 /* route_id */);
remote_endpoints_.emplace_back();
remote_endpoints_.back().BindWithProviderHostInfo(&info);
auto container = std::make_unique<MockServiceWorkerContainer>(
std::move(*remote_endpoints_.back().client_request()));
// Create an installing version and then start the navigation.
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
registration1_->SetInstallingVersion(version);
// Finish the navigation.
FinishNavigation(host.get(), std::move(info));
// Promote the worker to active while navigation is still happening.
registration1_->SetActiveVersion(version);
base::RunLoop().RunUntilIdle();
// The page should not be controlled since there was no active version at the
// time navigation started. Furthermore, no SetController IPC should have been
// sent.
EXPECT_FALSE(host->controller());
EXPECT_FALSE(container->was_set_controller_called());
// However, the host should know the registration is its best match, for
// .ready and claim().
EXPECT_EQ(registration1_.get(), host->MatchRegistration());
}
TEST_P(ServiceWorkerProviderHostTest,
Register_ContentSettingsDisallowsServiceWorker) {
ServiceWorkerTestContentBrowserClient test_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&test_browser_client);
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHostWithSiteForCookies(
GURL("https://www.example.com/foo"),
GURL("https://www.example.com/top"));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kDisabled,
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/scope"),
GURL("https://www.example.com/bar")));
ASSERT_EQ(1ul, test_browser_client.logs().size());
EXPECT_EQ(GURL("https://www.example.com/scope"),
test_browser_client.logs()[0].scope);
EXPECT_EQ(GURL("https://www.example.com/top"),
test_browser_client.logs()[0].first_party);
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kDisabled,
GetRegistration(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/")));
ASSERT_EQ(2ul, test_browser_client.logs().size());
EXPECT_EQ(GURL("https://www.example.com/foo"),
test_browser_client.logs()[1].scope);
EXPECT_EQ(GURL("https://www.example.com/top"),
test_browser_client.logs()[1].first_party);
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kDisabled,
GetRegistrations(remote_endpoint.host_ptr()->get()));
ASSERT_EQ(3ul, test_browser_client.logs().size());
EXPECT_EQ(GURL("https://www.example.com/foo"),
test_browser_client.logs()[2].scope);
EXPECT_EQ(GURL("https://www.example.com/top"),
test_browser_client.logs()[2].first_party);
SetBrowserClientForTesting(old_browser_client);
}
TEST_P(ServiceWorkerProviderHostTest, AllowsServiceWorker) {
// Create an active version.
scoped_refptr<ServiceWorkerVersion> version =
base::MakeRefCounted<ServiceWorkerVersion>(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
registration1_->SetActiveVersion(version);
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
base::WeakPtr<ServiceWorkerProviderHost> host =
CreateProviderHostForServiceWorkerContext(
helper_->mock_render_process_id(), true /* is_parent_frame_secure */,
version.get(), helper_->context()->AsWeakPtr(), &remote_endpoint);
ServiceWorkerTestContentBrowserClient test_browser_client;
ContentBrowserClient* old_browser_client =
SetBrowserClientForTesting(&test_browser_client);
EXPECT_FALSE(host->AllowServiceWorker(GURL("https://www.example.com/scope")));
ASSERT_EQ(1ul, test_browser_client.logs().size());
EXPECT_EQ(GURL("https://www.example.com/scope"),
test_browser_client.logs()[0].scope);
EXPECT_EQ(GURL("https://www.example.com/sw.js"),
test_browser_client.logs()[0].first_party);
SetBrowserClientForTesting(old_browser_client);
}
TEST_P(ServiceWorkerProviderHostTest, Register_HTTPS) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/"),
GURL("https://www.example.com/bar")));
}
TEST_P(ServiceWorkerProviderHostTest, Register_NonSecureTransportLocalhost) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("http://127.0.0.3:81/foo"));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
Register(remote_endpoint.host_ptr()->get(),
GURL("http://127.0.0.3:81/bar"),
GURL("http://127.0.0.3:81/baz")));
}
TEST_P(ServiceWorkerProviderHostTest, Register_InvalidScopeShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(), GURL(""),
GURL("https://www.example.com/bar/hoge.js"));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, Register_InvalidScriptShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/bar/"), GURL(""));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, Register_NonSecureOriginShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("http://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(), GURL("http://www.example.com/"),
GURL("http://www.example.com/bar"));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, Register_CrossOriginShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
// Script has a different host
Register(remote_endpoint.host_ptr()->get(), GURL("https://www.example.com/"),
GURL("https://foo.example.com/bar"));
EXPECT_EQ(1u, bad_messages_.size());
// Scope has a different host
Register(remote_endpoint.host_ptr()->get(), GURL("https://foo.example.com/"),
GURL("https://www.example.com/bar"));
EXPECT_EQ(2u, bad_messages_.size());
// Script has a different port
Register(remote_endpoint.host_ptr()->get(), GURL("https://www.example.com/"),
GURL("https://www.example.com:8080/bar"));
EXPECT_EQ(3u, bad_messages_.size());
// Scope has a different transport
Register(remote_endpoint.host_ptr()->get(), GURL("wss://www.example.com/"),
GURL("https://www.example.com/bar"));
EXPECT_EQ(4u, bad_messages_.size());
// Script and scope have a different host but match each other
Register(remote_endpoint.host_ptr()->get(), GURL("https://foo.example.com/"),
GURL("https://foo.example.com/bar"));
EXPECT_EQ(5u, bad_messages_.size());
// Script and scope URLs are invalid
Register(remote_endpoint.host_ptr()->get(), GURL(), GURL("h@ttps://@"));
EXPECT_EQ(6u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, Register_BadCharactersShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/%2f"),
GURL("https://www.example.com/"));
EXPECT_EQ(1u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/%2F"),
GURL("https://www.example.com/"));
EXPECT_EQ(2u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(), GURL("https://www.example.com/"),
GURL("https://www.example.com/%2f"));
EXPECT_EQ(3u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/%5c"),
GURL("https://www.example.com/"));
EXPECT_EQ(4u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(), GURL("https://www.example.com/"),
GURL("https://www.example.com/%5c"));
EXPECT_EQ(5u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(), GURL("https://www.example.com/"),
GURL("https://www.example.com/%5C"));
EXPECT_EQ(6u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, Register_FileSystemDocumentShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(
GURL("filesystem:https://www.example.com/temporary/a"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(),
GURL("filesystem:https://www.example.com/temporary/"),
GURL("https://www.example.com/temporary/bar"));
EXPECT_EQ(1u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(2u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("filesystem:https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(3u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest,
Register_FileSystemScriptOrScopeShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(
GURL("https://www.example.com/temporary/"));
ASSERT_TRUE(bad_messages_.empty());
Register(remote_endpoint.host_ptr()->get(),
GURL("filesystem:https://www.example.com/temporary/"),
GURL("https://www.example.com/temporary/bar"));
EXPECT_EQ(1u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(2u, bad_messages_.size());
Register(remote_endpoint.host_ptr()->get(),
GURL("filesystem:https://www.example.com/temporary/"),
GURL("filesystem:https://www.example.com/temporary/bar"));
EXPECT_EQ(3u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, EarlyContextDeletion) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
helper_->ShutdownContext();
// Let the shutdown reach the simulated IO thread.
base::RunLoop().RunUntilIdle();
// Because ServiceWorkerContextCore owns ServiceWorkerProviderHost, our
// ServiceWorkerProviderHost instance has destroyed.
EXPECT_TRUE(remote_endpoint.host_ptr()->encountered_error());
}
TEST_P(ServiceWorkerProviderHostTest, GetRegistration_Success) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
const GURL kScope("https://www.example.com/");
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
Register(remote_endpoint.host_ptr()->get(), kScope,
GURL("https://www.example.com/sw.js")));
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info;
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
GetRegistration(remote_endpoint.host_ptr()->get(), kScope, &info));
ASSERT_TRUE(info);
EXPECT_EQ(kScope, info->options->scope);
}
TEST_P(ServiceWorkerProviderHostTest,
GetRegistration_NotFoundShouldReturnNull) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info;
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
GetRegistration(remote_endpoint.host_ptr()->get(),
GURL("https://www.example.com/"), &info));
EXPECT_FALSE(info);
}
TEST_P(ServiceWorkerProviderHostTest, GetRegistration_CrossOriginShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
GetRegistration(remote_endpoint.host_ptr()->get(),
GURL("https://foo.example.com/"));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, GetRegistration_InvalidScopeShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
GetRegistration(remote_endpoint.host_ptr()->get(), GURL(""));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest,
GetRegistration_NonSecureOriginShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("http://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
GetRegistration(remote_endpoint.host_ptr()->get(),
GURL("http://www.example.com/"));
EXPECT_EQ(1u, bad_messages_.size());
}
TEST_P(ServiceWorkerProviderHostTest, GetRegistrations_SecureOrigin) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("https://www.example.com/foo"));
EXPECT_EQ(blink::mojom::ServiceWorkerErrorType::kNone,
GetRegistrations(remote_endpoint.host_ptr()->get()));
}
TEST_P(ServiceWorkerProviderHostTest,
GetRegistrations_NonSecureOriginShouldFail) {
ServiceWorkerRemoteProviderEndpoint remote_endpoint =
PrepareServiceWorkerProviderHost(GURL("http://www.example.com/foo"));
ASSERT_TRUE(bad_messages_.empty());
GetRegistrations(remote_endpoint.host_ptr()->get());
EXPECT_EQ(1u, bad_messages_.size());
}
// Test that a "reserved" (i.e., not execution ready) client is not included
// when iterating over client provider hosts. If it were, it'd be undesirably
// exposed via the Clients API.
TEST_P(ServiceWorkerProviderHostTest,
ReservedClientsAreNotExposedToClientsAPI) {
{
auto provider_info =
blink::mojom::ServiceWorkerProviderInfoForSharedWorker::New();
base::WeakPtr<ServiceWorkerProviderHost> host =
ServiceWorkerProviderHost::PreCreateForSharedWorker(
context_->AsWeakPtr(), helper_->mock_render_process_id(),
&provider_info);
const GURL url("https://www.example.com/shared_worker.js");
host->UpdateUrls(url, url);
EXPECT_FALSE(CanFindClientProviderHost(host.get()));
host->CompleteSharedWorkerPreparation();
EXPECT_TRUE(CanFindClientProviderHost(host.get()));
}
{
base::WeakPtr<ServiceWorkerProviderHost> host =
ServiceWorkerProviderHost::PreCreateNavigationHost(
helper_->context()->AsWeakPtr(), true,
base::RepeatingCallback<WebContents*(void)>());
blink::mojom::ServiceWorkerProviderHostInfoPtr info =
CreateProviderHostInfoForWindow(host->provider_id(), 1 /* route_id */);
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
remote_endpoint.BindWithProviderHostInfo(&info);
GURL url = GURL("https://www.example.com/page");
host->UpdateUrls(url, url);
EXPECT_FALSE(CanFindClientProviderHost(host.get()));
FinishNavigation(host.get(), std::move(info));
EXPECT_TRUE(CanFindClientProviderHost(host.get()));
}
}
// Tests that the service worker involved with a navigation (via
// AddServiceWorkerToUpdate) is updated when the host for the navigation is
// destroyed.
TEST_P(ServiceWorkerProviderHostTest, UpdateServiceWorkerOnDestruction) {
// This code path only is used in S13nSW.
if (!IsServiceWorkerServicificationEnabled())
return;
// Make a window.
ServiceWorkerProviderHost* host =
CreateProviderHost(GURL("https://www.example.com/example.html"));
// Make an active version.
auto version1 = base::MakeRefCounted<ServiceWorkerVersion>(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version1->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration1_->SetActiveVersion(version1);
auto version2 = base::MakeRefCounted<ServiceWorkerVersion>(
registration2_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 2 /* version_id */,
helper_->context()->AsWeakPtr());
version2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version2->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration2_->SetActiveVersion(version1);
host->AddServiceWorkerToUpdate(version1);
host->AddServiceWorkerToUpdate(version2);
ExpectUpdateIsNotScheduled(version1.get());
ExpectUpdateIsNotScheduled(version2.get());
// Destroy the provider host.
ASSERT_TRUE(remote_endpoints_.back().host_ptr()->is_bound());
remote_endpoints_.back().host_ptr()->reset();
base::RunLoop().RunUntilIdle();
// The provider host's destructor should have scheduled the update.
ExpectUpdateIsScheduled(version1.get());
ExpectUpdateIsScheduled(version2.get());
}
// Tests that the service worker involved with a navigation is updated when the
// host receives a HintToUpdateServiceWorker message.
TEST_P(ServiceWorkerProviderHostTest, HintToUpdateServiceWorker) {
// This code path only is used in S13nSW.
if (!IsServiceWorkerServicificationEnabled())
return;
// Make an active version.
auto version1 = base::MakeRefCounted<ServiceWorkerVersion>(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version1->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration1_->SetActiveVersion(version1);
// Make a window.
ServiceWorkerProviderHost* host =
CreateProviderHost(GURL("https://www.example.com/example.html"));
// Mark the service worker as needing update. Update should not be scheduled
// yet.
host->AddServiceWorkerToUpdate(version1);
ExpectUpdateIsNotScheduled(version1.get());
EXPECT_TRUE(HasVersionToUpdate(host));
// Send the hint from the renderer. Update should be scheduled.
blink::mojom::ServiceWorkerContainerHostAssociatedPtr* host_ptr =
remote_endpoints_.back().host_ptr();
(*host_ptr)->HintToUpdateServiceWorker();
base::RunLoop().RunUntilIdle();
ExpectUpdateIsScheduled(version1.get());
EXPECT_FALSE(HasVersionToUpdate(host));
}
// Tests that the host receives a HintToUpdateServiceWorker message but
// there was no service worker at main resource request time. This
// can happen due to claim().
TEST_P(ServiceWorkerProviderHostTest,
HintToUpdateServiceWorkerButNoVersionToUpdate) {
// This code path only is used in S13nSW.
if (!IsServiceWorkerServicificationEnabled())
return;
// Make a window.
ServiceWorkerProviderHost* host =
CreateProviderHost(GURL("https://www.example.com/example.html"));
// Make an active version.
auto version1 = base::MakeRefCounted<ServiceWorkerVersion>(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version1->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration1_->SetActiveVersion(version1);
// Pretend the registration gets associated after the main
// resource request, so AddServiceWorkerToUpdate() is not called.
ExpectUpdateIsNotScheduled(version1.get());
EXPECT_FALSE(HasVersionToUpdate(host));
// Send the hint from the renderer. Update should not be scheduled, since
// AddServiceWorkerToUpdate() was not called.
blink::mojom::ServiceWorkerContainerHostAssociatedPtr* host_ptr =
remote_endpoints_.back().host_ptr();
(*host_ptr)->HintToUpdateServiceWorker();
base::RunLoop().RunUntilIdle();
ExpectUpdateIsNotScheduled(version1.get());
EXPECT_FALSE(HasVersionToUpdate(host));
}
TEST_P(ServiceWorkerProviderHostTest, HintToUpdateServiceWorkerMultiple) {
// This code path only is used in S13nSW.
if (!IsServiceWorkerServicificationEnabled())
return;
// Make active versions.
auto version1 = base::MakeRefCounted<ServiceWorkerVersion>(
registration1_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 1 /* version_id */,
helper_->context()->AsWeakPtr());
version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version1->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration1_->SetActiveVersion(version1);
auto version2 = base::MakeRefCounted<ServiceWorkerVersion>(
registration2_.get(), GURL("https://www.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 2 /* version_id */,
helper_->context()->AsWeakPtr());
version2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version2->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration2_->SetActiveVersion(version1);
auto version3 = base::MakeRefCounted<ServiceWorkerVersion>(
registration3_.get(), GURL("https://other.example.com/sw.js"),
blink::mojom::ScriptType::kClassic, 3 /* version_id */,
helper_->context()->AsWeakPtr());
version3->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version3->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration3_->SetActiveVersion(version1);
// Make a window.
ServiceWorkerProviderHost* host =
CreateProviderHost(GURL("https://www.example.com/example.html"));
// Mark the service worker as needing update. Update should not be scheduled
// yet.
host->AddServiceWorkerToUpdate(version1);
host->AddServiceWorkerToUpdate(version2);
host->AddServiceWorkerToUpdate(version3);
ExpectUpdateIsNotScheduled(version1.get());
ExpectUpdateIsNotScheduled(version2.get());
ExpectUpdateIsNotScheduled(version3.get());
EXPECT_TRUE(HasVersionToUpdate(host));
// Pretend another page also used version3.
version3->IncrementPendingUpdateHintCount();
// Send the hint from the renderer. Update should be scheduled except for
// |version3| as it's being used by another page.
blink::mojom::ServiceWorkerContainerHostAssociatedPtr* host_ptr =
remote_endpoints_.back().host_ptr();
(*host_ptr)->HintToUpdateServiceWorker();
base::RunLoop().RunUntilIdle();
ExpectUpdateIsScheduled(version1.get());
ExpectUpdateIsScheduled(version2.get());
ExpectUpdateIsNotScheduled(version3.get());
EXPECT_FALSE(HasVersionToUpdate(host));
// Pretend the other page also finished for version3.
version3->DecrementPendingUpdateHintCount();
ExpectUpdateIsScheduled(version3.get());
}
INSTANTIATE_TEST_CASE_P(IsServiceWorkerServicificationEnabled,
ServiceWorkerProviderHostTest,
::testing::Bool(););
} // namespace content