blob: 3a9318b83fc8e4a9da24e702942d987cad5e93a3 [file] [log] [blame]
// Copyright 2015 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_client_utils.h"
#include <algorithm>
#include <tuple>
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.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_provider_host.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/service_worker/service_worker_client_info.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/child_process_host.h"
#include "url/gurl.h"
namespace content {
namespace service_worker_client_utils {
namespace {
using OpenURLCallback = base::Callback<void(int, int)>;
using GetWindowClientsCallback =
base::Callback<void(std::unique_ptr<ServiceWorkerClients>)>;
// The OpenURLObserver class is a WebContentsObserver that will wait for a
// WebContents to be initialized, run the |callback| passed to its constructor
// then self destroy.
// The callback will receive the process and frame ids. If something went wrong
// those will be (kInvalidUniqueID, MSG_ROUTING_NONE).
// The callback will be called in the IO thread.
class OpenURLObserver : public WebContentsObserver {
public:
OpenURLObserver(WebContents* web_contents,
int frame_tree_node_id,
const OpenURLCallback& callback)
: WebContentsObserver(web_contents),
frame_tree_node_id_(frame_tree_node_id),
callback_(callback) {}
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
DCHECK(web_contents());
if (!navigation_handle->HasCommitted()) {
// Return error.
RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
return;
}
if (navigation_handle->GetFrameTreeNodeId() != frame_tree_node_id_) {
// Return error.
RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
return;
}
RenderFrameHost* render_frame_host =
navigation_handle->GetRenderFrameHost();
RunCallback(render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID());
}
void RenderProcessGone(base::TerminationStatus status) override {
RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
}
void WebContentsDestroyed() override {
RunCallback(ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
}
private:
void RunCallback(int render_process_id, int render_frame_id) {
// After running the callback, |this| will stop observing, thus
// web_contents() should return nullptr and |RunCallback| should no longer
// be called. Then, |this| will self destroy.
DCHECK(web_contents());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback_, render_process_id, render_frame_id));
Observe(nullptr);
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
}
int frame_tree_node_id_;
const OpenURLCallback callback_;
DISALLOW_COPY_AND_ASSIGN(OpenURLObserver);
};
ServiceWorkerClientInfo GetWindowClientInfoOnUI(
int render_process_id,
int render_frame_id,
base::TimeTicks create_time,
const std::string& client_uuid) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
if (!render_frame_host)
return ServiceWorkerClientInfo();
// TODO(mlamouri,michaeln): it is possible to end up collecting information
// for a frame that is actually being navigated and isn't exactly what we are
// expecting.
return ServiceWorkerClientInfo(
client_uuid, render_frame_host->GetVisibilityState(),
render_frame_host->IsFocused(), render_frame_host->GetLastCommittedURL(),
render_frame_host->GetParent() ? REQUEST_CONTEXT_FRAME_TYPE_NESTED
: REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL,
render_frame_host->frame_tree_node()->last_focus_time(), create_time,
blink::kWebServiceWorkerClientTypeWindow);
}
ServiceWorkerClientInfo FocusOnUI(int render_process_id,
int render_frame_id,
base::TimeTicks create_time,
const std::string& client_uuid) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(render_frame_host));
if (!render_frame_host || !web_contents)
return ServiceWorkerClientInfo();
FrameTreeNode* frame_tree_node = render_frame_host->frame_tree_node();
// Focus the frame in the frame tree node, in case it has changed.
frame_tree_node->frame_tree()->SetFocusedFrame(
frame_tree_node, render_frame_host->GetSiteInstance());
// Focus the frame's view to make sure the frame is now considered as focused.
render_frame_host->GetView()->Focus();
// Move the web contents to the foreground.
web_contents->Activate();
return GetWindowClientInfoOnUI(render_process_id, render_frame_id,
create_time, client_uuid);
}
// This is only called for main frame navigations in OpenWindowOnUI().
void DidOpenURLOnUI(const OpenURLCallback& callback,
WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, ChildProcessHost::kInvalidUniqueID,
MSG_ROUTING_NONE));
return;
}
// ContentBrowserClient::OpenURL calls ui::BaseWindow::Show which
// makes the destination window the main+key window, but won't make Chrome
// the active application (https://crbug.com/470830). Since OpenWindow is
// always called from a user gesture (e.g. notification click), we should
// explicitly activate the window, which brings Chrome to the front.
static_cast<WebContentsImpl*>(web_contents)->Activate();
RenderFrameHostImpl* rfhi =
static_cast<RenderFrameHostImpl*>(web_contents->GetMainFrame());
new OpenURLObserver(web_contents,
rfhi->frame_tree_node()->frame_tree_node_id(), callback);
}
void OpenWindowOnUI(
const GURL& url,
const GURL& script_url,
int worker_process_id,
const scoped_refptr<ServiceWorkerContextWrapper>& context_wrapper,
WindowOpenDisposition disposition,
const OpenURLCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserContext* browser_context =
context_wrapper->storage_partition()
? context_wrapper->storage_partition()->browser_context()
: nullptr;
// We are shutting down.
if (!browser_context)
return;
RenderProcessHost* render_process_host =
RenderProcessHost::FromID(worker_process_id);
if (render_process_host->IsForGuestsOnly()) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, ChildProcessHost::kInvalidUniqueID,
MSG_ROUTING_NONE));
return;
}
OpenURLParams params(
url,
Referrer::SanitizeForRequest(
url, Referrer(script_url, blink::kWebReferrerPolicyDefault)),
disposition, ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
true /* is_renderer_initiated */);
GetContentClient()->browser()->OpenURL(browser_context, params,
base::Bind(&DidOpenURLOnUI, callback));
}
void NavigateClientOnUI(const GURL& url,
const GURL& script_url,
int process_id,
int frame_id,
const OpenURLCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* rfhi = RenderFrameHostImpl::FromID(process_id, frame_id);
WebContents* web_contents = WebContents::FromRenderFrameHost(rfhi);
if (!rfhi || !web_contents) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(callback, ChildProcessHost::kInvalidUniqueID,
MSG_ROUTING_NONE));
return;
}
ui::PageTransition transition = rfhi->GetParent()
? ui::PAGE_TRANSITION_AUTO_SUBFRAME
: ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
int frame_tree_node_id = rfhi->frame_tree_node()->frame_tree_node_id();
OpenURLParams params(
url,
Referrer::SanitizeForRequest(
url, Referrer(script_url, blink::kWebReferrerPolicyDefault)),
frame_tree_node_id, WindowOpenDisposition::CURRENT_TAB, transition,
true /* is_renderer_initiated */);
web_contents->OpenURL(params);
new OpenURLObserver(web_contents, frame_tree_node_id, callback);
}
void DidNavigate(const base::WeakPtr<ServiceWorkerContextCore>& context,
const GURL& origin,
const NavigationCallback& callback,
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!context) {
callback.Run(SERVICE_WORKER_ERROR_ABORT, ServiceWorkerClientInfo());
return;
}
if (render_process_id == ChildProcessHost::kInvalidUniqueID &&
render_frame_id == MSG_ROUTING_NONE) {
callback.Run(SERVICE_WORKER_ERROR_FAILED, ServiceWorkerClientInfo());
return;
}
for (std::unique_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
context->GetClientProviderHostIterator(origin);
!it->IsAtEnd(); it->Advance()) {
ServiceWorkerProviderHost* provider_host = it->GetProviderHost();
if (provider_host->process_id() != render_process_id ||
provider_host->frame_id() != render_frame_id) {
continue;
}
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::UI, FROM_HERE,
base::Bind(&GetWindowClientInfoOnUI, provider_host->process_id(),
provider_host->route_id(), provider_host->create_time(),
provider_host->client_uuid()),
base::Bind(callback, SERVICE_WORKER_OK));
return;
}
// If here, it means that no provider_host was found, in which case, the
// renderer should still be informed that the window was opened.
callback.Run(SERVICE_WORKER_OK, ServiceWorkerClientInfo());
}
void AddWindowClient(
ServiceWorkerProviderHost* host,
std::vector<std::tuple<int, int, base::TimeTicks, std::string>>*
client_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (host->client_type() != blink::kWebServiceWorkerClientTypeWindow)
return;
client_info->push_back(std::make_tuple(host->process_id(), host->frame_id(),
host->create_time(),
host->client_uuid()));
}
void AddNonWindowClient(ServiceWorkerProviderHost* host,
const ServiceWorkerClientQueryOptions& options,
ServiceWorkerClients* clients) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
blink::WebServiceWorkerClientType host_client_type = host->client_type();
if (host_client_type == blink::kWebServiceWorkerClientTypeWindow)
return;
if (options.client_type != blink::kWebServiceWorkerClientTypeAll &&
options.client_type != host_client_type)
return;
ServiceWorkerClientInfo client_info(
host->client_uuid(), blink::kWebPageVisibilityStateHidden,
false, // is_focused
host->document_url(), REQUEST_CONTEXT_FRAME_TYPE_NONE, base::TimeTicks(),
host->create_time(), host_client_type);
clients->push_back(client_info);
}
void OnGetWindowClientsOnUI(
// The tuple contains process_id, frame_id, create_time, client_uuid.
const std::vector<std::tuple<int, int, base::TimeTicks, std::string>>&
clients_info,
const GURL& script_url,
const GetWindowClientsCallback& callback,
std::unique_ptr<ServiceWorkerClients> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
for (const auto& it : clients_info) {
ServiceWorkerClientInfo info = GetWindowClientInfoOnUI(
std::get<0>(it), std::get<1>(it), std::get<2>(it), std::get<3>(it));
// If the request to the provider_host returned an empty
// ServiceWorkerClientInfo, that means that it wasn't possible to associate
// it with a valid RenderFrameHost. It might be because the frame was killed
// or navigated in between.
if (info.IsEmpty())
continue;
// We can get info for a frame that was navigating end ended up with a
// different URL than expected. In such case, we should make sure to not
// expose cross-origin WindowClient.
if (info.url.GetOrigin() != script_url.GetOrigin())
continue;
clients->push_back(info);
}
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, base::Passed(&clients)));
}
struct ServiceWorkerClientInfoSort {
bool operator()(const ServiceWorkerClientInfo& a,
const ServiceWorkerClientInfo& b) const {
// Clients for windows should be appeared earlier.
if (a.client_type == blink::kWebServiceWorkerClientTypeWindow &&
b.client_type != blink::kWebServiceWorkerClientTypeWindow) {
return true;
}
if (a.client_type != blink::kWebServiceWorkerClientTypeWindow &&
b.client_type == blink::kWebServiceWorkerClientTypeWindow) {
return false;
}
// Clients focused recently should be appeared earlier.
if (a.last_focus_time != b.last_focus_time)
return a.last_focus_time > b.last_focus_time;
// Clients created before should be appeared earlier.
return a.create_time < b.create_time;
}
};
void DidGetClients(const ClientsCallback& callback,
std::unique_ptr<ServiceWorkerClients> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::sort(clients->begin(), clients->end(), ServiceWorkerClientInfoSort());
callback.Run(std::move(clients));
}
void GetNonWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
const ServiceWorkerClientQueryOptions& options,
const ClientsCallback& callback,
std::unique_ptr<ServiceWorkerClients> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!options.include_uncontrolled) {
for (auto& controllee : controller->controllee_map())
AddNonWindowClient(controllee.second, options, clients.get());
} else if (controller->context()) {
GURL origin = controller->script_url().GetOrigin();
for (auto it = controller->context()->GetClientProviderHostIterator(origin);
!it->IsAtEnd(); it->Advance()) {
AddNonWindowClient(it->GetProviderHost(), options, clients.get());
}
}
DidGetClients(callback, std::move(clients));
}
void DidGetWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
const ServiceWorkerClientQueryOptions& options,
const ClientsCallback& callback,
std::unique_ptr<ServiceWorkerClients> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (options.client_type == blink::kWebServiceWorkerClientTypeAll) {
GetNonWindowClients(controller, options, callback, std::move(clients));
return;
}
DidGetClients(callback, std::move(clients));
}
void GetWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
const ServiceWorkerClientQueryOptions& options,
const ClientsCallback& callback,
std::unique_ptr<ServiceWorkerClients> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(options.client_type == blink::kWebServiceWorkerClientTypeWindow ||
options.client_type == blink::kWebServiceWorkerClientTypeAll);
std::vector<std::tuple<int, int, base::TimeTicks, std::string>> clients_info;
if (!options.include_uncontrolled) {
for (auto& controllee : controller->controllee_map())
AddWindowClient(controllee.second, &clients_info);
} else if (controller->context()) {
GURL origin = controller->script_url().GetOrigin();
for (auto it = controller->context()->GetClientProviderHostIterator(origin);
!it->IsAtEnd(); it->Advance()) {
AddWindowClient(it->GetProviderHost(), &clients_info);
}
}
if (clients_info.empty()) {
DidGetWindowClients(controller, options, callback, std::move(clients));
return;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&OnGetWindowClientsOnUI, clients_info, controller->script_url(),
base::Bind(&DidGetWindowClients, controller, options, callback),
base::Passed(&clients)));
}
} // namespace
void FocusWindowClient(ServiceWorkerProviderHost* provider_host,
const ClientCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(blink::kWebServiceWorkerClientTypeWindow,
provider_host->client_type());
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::UI, FROM_HERE,
base::Bind(&FocusOnUI, provider_host->process_id(),
provider_host->frame_id(), provider_host->create_time(),
provider_host->client_uuid()),
callback);
}
void OpenWindow(const GURL& url,
const GURL& script_url,
int worker_process_id,
const base::WeakPtr<ServiceWorkerContextCore>& context,
WindowOpenDisposition disposition,
const NavigationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&OpenWindowOnUI, url, script_url, worker_process_id,
make_scoped_refptr(context->wrapper()), disposition,
base::Bind(&DidNavigate, context, script_url.GetOrigin(), callback)));
}
void NavigateClient(const GURL& url,
const GURL& script_url,
int process_id,
int frame_id,
const base::WeakPtr<ServiceWorkerContextCore>& context,
const NavigationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&NavigateClientOnUI, url, script_url, process_id, frame_id,
base::Bind(&DidNavigate, context, script_url.GetOrigin(), callback)));
}
void GetClient(ServiceWorkerProviderHost* provider_host,
const ClientCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
blink::WebServiceWorkerClientType client_type = provider_host->client_type();
DCHECK(client_type == blink::kWebServiceWorkerClientTypeWindow ||
client_type == blink::kWebServiceWorkerClientTypeWorker ||
client_type == blink::kWebServiceWorkerClientTypeSharedWorker)
<< client_type;
if (client_type == blink::kWebServiceWorkerClientTypeWindow) {
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::UI, FROM_HERE,
base::Bind(&GetWindowClientInfoOnUI, provider_host->process_id(),
provider_host->route_id(), provider_host->create_time(),
provider_host->client_uuid()),
callback);
return;
}
ServiceWorkerClientInfo client_info(
provider_host->client_uuid(), blink::kWebPageVisibilityStateHidden,
false, // is_focused
provider_host->document_url(), REQUEST_CONTEXT_FRAME_TYPE_NONE,
base::TimeTicks(), provider_host->create_time(),
provider_host->client_type());
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, client_info));
}
void GetClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
const ServiceWorkerClientQueryOptions& options,
const ClientsCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto clients = base::MakeUnique<ServiceWorkerClients>();
if (!controller->HasControllee() && !options.include_uncontrolled) {
DidGetClients(callback, std::move(clients));
return;
}
// For Window clients we want to query the info on the UI thread first.
if (options.client_type == blink::kWebServiceWorkerClientTypeWindow ||
options.client_type == blink::kWebServiceWorkerClientTypeAll) {
GetWindowClients(controller, options, callback, std::move(clients));
return;
}
GetNonWindowClients(controller, options, callback, std::move(clients));
}
} // namespace service_worker_client_utils
} // namespace content