Index: content/browser/service_worker/service_worker_version.cc |
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc |
index 47545e04e6d98d290f3f3647d603b1456f4d135a..e767a3a94e731e1c207fa5b564dc7dfe259c0e9f 100644 |
--- a/content/browser/service_worker/service_worker_version.cc |
+++ b/content/browser/service_worker/service_worker_version.cc |
@@ -14,11 +14,22 @@ |
#include "content/browser/service_worker/embedded_worker_instance.h" |
#include "content/browser/service_worker/embedded_worker_registry.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_registration.h" |
#include "content/browser/service_worker/service_worker_utils.h" |
+#include "content/browser/storage_partition_impl.h" |
#include "content/common/service_worker/service_worker_messages.h" |
#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/content_browser_client.h" |
+#include "content/public/browser/page_navigator.h" |
+#include "content/public/browser/render_frame_host.h" |
+#include "content/public/browser/render_process_host.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 "content/public/common/content_client.h" |
#include "content/public/common/content_switches.h" |
+#include "content/public/common/result_codes.h" |
#include "net/http/http_response_info.h" |
namespace content { |
@@ -151,6 +162,110 @@ void RunErrorCrossOriginConnectCallback( |
callback.Run(status, false); |
} |
+using WindowOpenedCallback = base::Callback<void(int, int)>; |
+ |
+// The WindowOpenedObserver class is a WebContentsObserver that will wait for a |
+// new Window's 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 WindowOpenedObserver : public WebContentsObserver { |
+ public: |
+ WindowOpenedObserver(WebContents* web_contents, |
+ const WindowOpenedCallback& callback) |
+ : WebContentsObserver(web_contents), |
+ callback_(callback) |
+ {} |
+ |
+ void DidCommitProvisionalLoadForFrame( |
+ RenderFrameHost* render_frame_host, |
+ const GURL& validated_url, |
+ ui::PageTransition transition_type) override { |
+ DCHECK(web_contents()); |
+ |
+ if (render_frame_host != web_contents()->GetMainFrame()) |
+ return; |
+ |
+ 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::MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
+ } |
+ |
+ const WindowOpenedCallback callback_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(WindowOpenedObserver); |
+}; |
+ |
+void OpenWindowOnUI( |
+ const GURL& url, |
+ const GURL& script_url, |
+ int process_id, |
+ const scoped_refptr<ServiceWorkerContextWrapper>& context_wrapper, |
+ const WindowOpenedCallback& 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(process_id); |
+ if (render_process_host->IsIsolatedGuest()) { |
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, |
+ base::Bind(callback, |
+ ChildProcessHost::kInvalidUniqueID, |
+ MSG_ROUTING_NONE)); |
+ return; |
+ } |
+ |
+ OpenURLParams params(url, |
+ Referrer(script_url, blink::WebReferrerPolicyDefault), |
+ NEW_FOREGROUND_TAB, |
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL, |
+ true /* is_renderer_initiated */); |
+ |
+ WebContents* web_contents = |
+ GetContentClient()->browser()->OpenURL(browser_context, params); |
+ DCHECK(web_contents); |
+ |
+ new WindowOpenedObserver(web_contents, callback); |
+} |
+ |
+void KillEmbeddedWorkerProcess(int process_id, ResultCode code) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ |
+ RenderProcessHost* render_process_host = |
+ RenderProcessHost::FromID(process_id); |
+ if (render_process_host->GetHandle() != base::kNullProcessHandle) |
+ render_process_host->ReceivedBadMessage(); |
+} |
+ |
} // namespace |
ServiceWorkerVersion::ServiceWorkerVersion( |
@@ -807,6 +922,8 @@ bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message& message) { |
OnGeofencingEventFinished) |
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_CrossOriginConnectEventFinished, |
OnCrossOriginConnectEventFinished) |
+ IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_OpenWindow, |
+ OnOpenWindow) |
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument, |
OnPostMessageToDocument) |
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FocusClient, |
@@ -1029,6 +1146,89 @@ void ServiceWorkerVersion::OnCrossOriginConnectEventFinished( |
RemoveCallbackAndStopIfDoomed(&cross_origin_connect_callbacks_, request_id); |
} |
+void ServiceWorkerVersion::OnOpenWindow(int request_id, const GURL& url) { |
+ // Just abort if we are shutting down. |
+ if (!context_) |
+ return; |
+ |
+ if (url.GetOrigin() != script_url_.GetOrigin()) { |
+ // There should be a same origin check by Blink, if the request is still not |
+ // same origin, the process might be compromised and should be eliminated. |
+ DVLOG(1) << "Received a cross origin openWindow() request from a service " |
+ "worker. Killing associated process."; |
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
+ base::Bind(&KillEmbeddedWorkerProcess, |
+ embedded_worker_->process_id(), |
+ RESULT_CODE_KILLED_BAD_MESSAGE)); |
+ return; |
+ } |
+ |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&OpenWindowOnUI, |
+ url, |
+ script_url_, |
+ embedded_worker_->process_id(), |
+ make_scoped_refptr(context_->wrapper()), |
+ base::Bind(&ServiceWorkerVersion::DidOpenWindow, |
+ weak_factory_.GetWeakPtr(), |
+ request_id))); |
+} |
+ |
+void ServiceWorkerVersion::DidOpenWindow(int request_id, |
+ int render_process_id, |
+ int render_frame_id) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ |
+ if (running_status() != RUNNING) |
+ return; |
+ |
+ if (render_process_id == ChildProcessHost::kInvalidUniqueID && |
+ render_frame_id == MSG_ROUTING_NONE) { |
+ embedded_worker_->SendMessage(ServiceWorkerMsg_OpenWindowError(request_id)); |
+ return; |
+ } |
+ |
+ for (const auto& it : controllee_map_) { |
+ const ServiceWorkerProviderHost* provider_host = it.first; |
+ if (provider_host->process_id() != render_process_id || |
+ provider_host->frame_id() != render_frame_id) { |
+ continue; |
+ } |
+ |
+ // it.second is the client_id associated with the provider_host. |
+ provider_host->GetClientInfo( |
+ base::Bind(&ServiceWorkerVersion::OnOpenWindowFinished, |
+ weak_factory_.GetWeakPtr(), request_id, it.second)); |
+ 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. |
+ OnOpenWindowFinished(request_id, 0, ServiceWorkerClientInfo()); |
+} |
+ |
+void ServiceWorkerVersion::OnOpenWindowFinished( |
+ int request_id, |
+ int client_id, |
+ const ServiceWorkerClientInfo& client_info) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ |
+ if (running_status() != RUNNING) |
+ return; |
+ |
+ ServiceWorkerClientInfo client(client_info); |
+ |
+ // If the |client_info| is empty, it means that the opened window wasn't |
+ // controlled but the action still succeeded. The renderer process is |
+ // expecting an empty client in such case. |
+ if (!client.IsEmpty()) |
+ client.client_id = client_id; |
+ |
+ embedded_worker_->SendMessage(ServiceWorkerMsg_OpenWindowResponse( |
+ request_id, client)); |
+} |
+ |
void ServiceWorkerVersion::OnPostMessageToDocument( |
int client_id, |
const base::string16& message, |