blob: e24d7bd1558d2c44d682cbeeee337b57f1b9bcf5 [file] [log] [blame]
// Copyright 2018 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 <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_forward.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/platform_notification_context.h"
#include "content/public/browser/service_worker_context_observer.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/test_content_browser_client.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "url/gurl.h"
namespace content {
namespace {
// Waits for a service worker to be activated.
class ActivatedServiceWorkerObserver : public ServiceWorkerContextObserver {
public:
ActivatedServiceWorkerObserver() : ServiceWorkerContextObserver() {}
~ActivatedServiceWorkerObserver() override {}
void OnVersionActivated(int64_t version_id, const GURL& scope) override {
version_id_ = version_id;
if (activated_callback_)
std::move(activated_callback_).Run();
}
void WaitForActivated() {
if (version_id_ != -1)
return;
base::RunLoop loop;
activated_callback_ = loop.QuitClosure();
loop.Run();
}
int64_t version_id() const { return version_id_; }
private:
base::OnceClosure activated_callback_;
int64_t version_id_ = -1;
DISALLOW_COPY_AND_ASSIGN(ActivatedServiceWorkerObserver);
};
// Mainly for testing that ShouldAllowOpenURL() is properly consulted.
class ServiceWorkerClientsContentBrowserClient
: public TestContentBrowserClient {
public:
ServiceWorkerClientsContentBrowserClient() : TestContentBrowserClient() {}
~ServiceWorkerClientsContentBrowserClient() override {}
const GURL& opened_url() const { return opened_url_; }
void SetToAllowOpenURL(bool allow) { allow_open_url_ = allow; }
void WaitForOpenURL() {
if (!opened_url_.is_empty())
return;
base::RunLoop run_loop;
opened_url_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
// ContentBrowserClient overrides:
bool ShouldAllowOpenURL(SiteInstance* site_instance,
const GURL& url) override {
return allow_open_url_;
}
void OpenURL(
SiteInstance* site_instance,
const OpenURLParams& params,
const base::RepeatingCallback<void(WebContents*)>& callback) override {
opened_url_ = params.url;
callback.Run(nullptr);
if (opened_url_callback_)
std::move(opened_url_callback_).Run();
}
private:
bool allow_open_url_ = true;
GURL opened_url_;
base::OnceClosure opened_url_callback_;
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerClientsContentBrowserClient);
};
} // namespace
// Tests for the service worker Clients API.
class ServiceWorkerClientsApiBrowserTest : public ContentBrowserTest {
public:
ServiceWorkerClientsApiBrowserTest() = default;
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
ContentBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
old_content_browser_client_ =
SetBrowserClientForTesting(&content_browser_client_);
embedded_test_server()->StartAcceptingConnections();
StoragePartition* partition = BrowserContext::GetDefaultStoragePartition(
shell()->web_contents()->GetBrowserContext());
wrapper_ = static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
}
void TearDownOnMainThread() override {
SetBrowserClientForTesting(old_content_browser_client_);
}
void DispatchNotificationClickEvent(int64_t version_id) {
base::RunLoop run_loop;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&ServiceWorkerClientsApiBrowserTest::
DispatchNotificationClickEventOnIOThread,
base::Unretained(this), version_id,
run_loop.QuitClosure()));
run_loop.Run();
}
void DispatchNotificationClickEventOnIOThread(int64_t version_id,
base::OnceClosure done) {
ServiceWorkerVersion* version =
wrapper_->context()->GetLiveVersion(version_id);
ASSERT_TRUE(version);
version->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK,
base::BindOnce(&ServiceWorkerClientsApiBrowserTest::
DispatchNotificationClickAfterStartWorker,
base::Unretained(this), base::WrapRefCounted(version),
std::move(done)));
}
void DispatchNotificationClickAfterStartWorker(
scoped_refptr<ServiceWorkerVersion> version,
base::OnceClosure done,
blink::ServiceWorkerStatusCode status) {
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status);
version->StartRequest(ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK,
base::DoNothing());
version->endpoint()->DispatchNotificationClickEvent(
"notification_id", {} /* notification_data */, -1 /* action_index */,
base::nullopt /* reply */,
base::BindOnce(
[](base::OnceClosure done,
blink::mojom::ServiceWorkerEventStatus event_status) {
EXPECT_EQ(blink::mojom::ServiceWorkerEventStatus::COMPLETED,
event_status);
std::move(done).Run();
},
std::move(done)));
}
ServiceWorkerContextWrapper* wrapper() { return wrapper_.get(); }
protected:
ServiceWorkerClientsContentBrowserClient content_browser_client_;
private:
scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
ContentBrowserClient* old_content_browser_client_;
};
// Tests a successful WindowClient.navigate() call.
IN_PROC_BROWSER_TEST_F(ServiceWorkerClientsApiBrowserTest, Navigate) {
// Load a page that registers a service worker.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('client_api_worker.js');"));
// Load the page again so we are controlled.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ(true, EvalJs(shell(), "!!navigator.serviceWorker.controller"));
// Have the service worker call client.navigate() on the page.
const std::string navigate_script = R"(
(async () => {
const registration = await navigator.serviceWorker.ready;
registration.active.postMessage({command: 'navigate', url: 'empty.html'});
return true;
})();
)";
EXPECT_EQ(true, EvalJs(shell(), navigate_script));
// The page should be navigated to empty.html.
const base::string16 title =
base::ASCIIToUTF16("ServiceWorker test - empty page");
TitleWatcher title_watcher(shell()->web_contents(), title);
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
}
// Tests a WindowClient.navigate() call to a disallowed URL.
IN_PROC_BROWSER_TEST_F(ServiceWorkerClientsApiBrowserTest,
NavigateDisallowedUrl) {
content_browser_client_.SetToAllowOpenURL(false);
// Load a page that registers a service worker.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('client_api_worker.js');"));
// Load the page again so we are controlled.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ(true, EvalJs(shell(), "!!navigator.serviceWorker.controller"));
// Have the service worker call client.navigate() on the page.
const std::string navigate_script = R"(
(async () => {
const registration = await navigator.serviceWorker.ready;
registration.active.postMessage({command: 'navigate', url: 'empty.html'});
return true;
})();
)";
EXPECT_EQ(true, EvalJs(shell(), navigate_script));
// The page should be navigated to about:blank instead of the requested URL.
const base::string16 title = base::ASCIIToUTF16("about:blank");
TitleWatcher title_watcher(shell()->web_contents(), title);
EXPECT_EQ(title, title_watcher.WaitAndGetTitle());
}
// Tests a WindowClient.navigate() call during a browser-initiated navigation.
// Regression test for https://crbug.com/930154.
IN_PROC_BROWSER_TEST_F(ServiceWorkerClientsApiBrowserTest,
NavigateDuringBrowserNavigation) {
// Load a page that registers a service worker.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('client_api_worker.js');"));
// Load the test page.
EXPECT_TRUE(NavigateToURL(
shell(),
embedded_test_server()->GetURL("/service_worker/request_navigate.html")));
// Start a browser-initiated navigation.
GURL url(embedded_test_server()->GetURL("/title1.html"));
TestNavigationManager navigation(shell()->web_contents(), url);
shell()->LoadURL(url);
EXPECT_TRUE(navigation.WaitForRequestStart());
// Have the service worker call client.navigate() to try to go to another
// URL. It should fail.
EXPECT_EQ("navigate failed", EvalJs(shell(), "requestToNavigate();"));
// The browser-initiated navigation should finish.
navigation.WaitForNavigationFinished(); // Resume navigation.
EXPECT_TRUE(navigation.was_successful());
}
// Tests a successful Clients.openWindow() call.
IN_PROC_BROWSER_TEST_F(ServiceWorkerClientsApiBrowserTest, OpenWindow) {
ActivatedServiceWorkerObserver observer;
wrapper()->AddObserver(&observer);
// Load a page that registers a service worker.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('client_api_worker.js');"));
observer.WaitForActivated();
// Tell the service worker to call clients.openWindow(). Do it from a
// notification click event so it has a user interaction token that allows
// popups.
int64_t id = observer.version_id();
EXPECT_NE(-1, id);
DispatchNotificationClickEvent(id);
// OpenURL() should be called.
content_browser_client_.WaitForOpenURL();
EXPECT_EQ(embedded_test_server()->GetURL("/service_worker/empty.html"),
content_browser_client_.opened_url());
wrapper()->RemoveObserver(&observer);
}
// Tests a Clients.openWindow() call to a disallowed URL.
IN_PROC_BROWSER_TEST_F(ServiceWorkerClientsApiBrowserTest,
OpenWindowDisallowedUrl) {
content_browser_client_.SetToAllowOpenURL(false);
ActivatedServiceWorkerObserver observer;
wrapper()->AddObserver(&observer);
// Load a page that registers a service worker.
EXPECT_TRUE(NavigateToURL(shell(),
embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(shell(), "register('client_api_worker.js');"));
observer.WaitForActivated();
// Tell the service worker to call clients.openWindow(). Do it from a
// notification click event so it has a user interaction token that allows
// popups.
int64_t id = observer.version_id();
EXPECT_NE(-1, id);
DispatchNotificationClickEvent(id);
// OpenURL() should be called with about:blank instead of the requested URL.
content_browser_client_.WaitForOpenURL();
EXPECT_EQ(GURL("about:blank"), content_browser_client_.opened_url());
wrapper()->RemoveObserver(&observer);
}
} // namespace content