Provide child/frame IDs for WebSocket handshake request

AndroidCookiePolicy needs the child ID and the frame ID of a WebSocket
connection to determine if it allows the connection to attach
third-party cookies. This CL provide the additional information to the
WebSocket handshake net::URLRequest.

BUG=634311

Review-Url: https://codereview.chromium.org/2397393002
Cr-Commit-Position: refs/heads/master@{#427109}
diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc
index 1dc3ba9..f14d09f6 100644
--- a/android_webview/browser/aw_cookie_access_policy.cc
+++ b/android_webview/browser/aw_cookie_access_policy.cc
@@ -10,11 +10,13 @@
 #include "base/logging.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_request_info.h"
+#include "content/public/browser/websocket_handshake_request_info.h"
 #include "net/base/net_errors.h"
 
 using base::AutoLock;
 using content::BrowserThread;
 using content::ResourceRequestInfo;
+using content::WebSocketHandshakeRequestInfo;
 using net::StaticCookiePolicy;
 
 namespace android_webview {
@@ -57,12 +59,21 @@
 
 bool AwCookieAccessPolicy::GetShouldAcceptThirdPartyCookies(
     const net::URLRequest& request) {
+  int child_id = 0;
+  int frame_id = 0;
   const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(&request);
-  if (!info) {
-    return false;
+  if (info) {
+    child_id = info->GetChildID();
+    frame_id = info->GetRenderFrameID();
+  } else {
+    const WebSocketHandshakeRequestInfo* websocket_info =
+        WebSocketHandshakeRequestInfo::ForRequest(&request);
+    if (!websocket_info)
+      return false;
+    child_id = websocket_info->GetChildId();
+    frame_id = websocket_info->GetRenderFrameId();
   }
-  return GetShouldAcceptThirdPartyCookies(info->GetChildID(),
-                                          info->GetRenderFrameID());
+  return GetShouldAcceptThirdPartyCookies(child_id, frame_id);
 }
 
 bool AwCookieAccessPolicy::OnCanGetCookies(const net::URLRequest& request,
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
index c96fdeb..08c6e3e 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/CookieManagerTest.java
@@ -17,6 +17,8 @@
 import org.chromium.android_webview.test.util.CookieUtils;
 import org.chromium.android_webview.test.util.JSUtils;
 import org.chromium.base.test.util.Feature;
+import org.chromium.content.browser.test.util.JavaScriptUtils;
+import org.chromium.content_public.browser.WebContents;
 import org.chromium.net.test.util.TestWebServer;
 
 import java.util.ArrayList;
@@ -401,6 +403,51 @@
         }
     }
 
+    private String thirdPartyCookieForWebSocket(boolean acceptCookie) throws Throwable {
+        TestWebServer webServer = TestWebServer.start();
+        try {
+            // Turn global allow on.
+            mCookieManager.setAcceptCookie(true);
+            assertTrue(mCookieManager.acceptCookie());
+
+            // Sets the per-WebView value.
+            mAwContents.getSettings().setAcceptThirdPartyCookies(acceptCookie);
+            assertEquals(acceptCookie, mAwContents.getSettings().getAcceptThirdPartyCookies());
+
+            // |cookieUrl| is a third-party url that sets a cookie on response.
+            String cookieUrl = toThirdPartyUrl(
+                    makeCookieWebSocketUrl(webServer, "/cookie_1", "test1", "value1"));
+            // This html file includes a script establishing a WebSocket connection to |cookieUrl|.
+            String url = makeWebSocketScriptUrl(webServer, "/content_1.html", cookieUrl);
+            loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+            final String connecting = "0"; // WebSocket.CONNECTING
+            final String closed = "3"; // WebSocket.CLOSED
+            String readyState = connecting;
+            WebContents webContents = mAwContents.getWebContents();
+            while (!readyState.equals(closed)) {
+                readyState = JavaScriptUtils.executeJavaScriptAndWaitForResult(
+                        webContents, "ws.readyState");
+            }
+            assertEquals("true",
+                    JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, "hasOpened"));
+            return mCookieManager.getCookie(cookieUrl);
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
+    @MediumTest
+    @Feature({"AndroidWebView", "Privacy"})
+    public void testThirdPartyCookieForWebSocketDisabledCase() throws Throwable {
+        assertNull(thirdPartyCookieForWebSocket(false));
+    }
+
+    @MediumTest
+    @Feature({"AndroidWebView", "Privacy"})
+    public void testThirdPartyCookieForWebSocketEnabledCase() throws Throwable {
+        assertEquals("test1=value1", thirdPartyCookieForWebSocket(true));
+    }
+
     /**
      * Creates a response on the TestWebServer which attempts to set a cookie when fetched.
      * @param  webServer  the webServer on which to create the response
@@ -418,6 +465,22 @@
     }
 
     /**
+     * Creates a response on the TestWebServer which attempts to set a cookie when establishing a
+     * WebSocket connection.
+     * @param  webServer  the webServer on which to create the response
+     * @param  path the path component of the url (e.g "/cookie_test.html")
+     * @param  key the key of the cookie
+     * @param  value the value of the cookie
+     * @return  the url which gets the response
+     */
+    private String makeCookieWebSocketUrl(
+            TestWebServer webServer, String path, String key, String value) {
+        List<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>();
+        responseHeaders.add(Pair.create("Set-Cookie", key + "=" + value + "; path=" + path));
+        return webServer.setResponseForWebSocket(path, responseHeaders);
+    }
+
+    /**
      * Creates a response on the TestWebServer which contains a script tag with an external src.
      * @param  webServer  the webServer on which to create the response
      * @param  path the path component of the url (e.g "/my_thing_with_script.html")
@@ -430,6 +493,24 @@
         return webServer.setResponse(path, responseStr, null);
     }
 
+    /**
+     * Creates a response on the TestWebServer which contains a script establishing a WebSocket
+     * connection.
+     * @param  webServer  the webServer on which to create the response
+     * @param  path the path component of the url (e.g "/my_thing_with_script.html")
+     * @param  url the url which which should appear as the src of the script tag.
+     * @return  the url which gets the response
+     */
+    private String makeWebSocketScriptUrl(TestWebServer webServer, String path, String url) {
+        String responseStr = "<html><head><title>Content!</title></head>"
+                + "<body><script>\n"
+                + "let ws = new WebSocket('" + url.replaceAll("^http", "ws") + "');\n"
+                + "let hasOpened = false;\n"
+                + "ws.onopen = () => hasOpened = true;\n"
+                + "</script></body></html>";
+        return webServer.setResponse(path, responseStr, null);
+    }
+
     @MediumTest
     @Feature({"AndroidWebView", "Privacy"})
     public void testThirdPartyJavascriptCookie() throws Throwable {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 819f89f..9365a8de 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1349,6 +1349,8 @@
     "web_contents/web_drag_source_mac.mm",
     "web_contents/web_drag_utils_win.cc",
     "web_contents/web_drag_utils_win.h",
+    "websockets/websocket_handshake_request_info_impl.cc",
+    "websockets/websocket_handshake_request_info_impl.h",
     "websockets/websocket_impl.cc",
     "websockets/websocket_impl.h",
     "websockets/websocket_manager.cc",
diff --git a/content/browser/websockets/websocket_handshake_request_info_impl.cc b/content/browser/websockets/websocket_handshake_request_info_impl.cc
new file mode 100644
index 0000000..14bb5cb1
--- /dev/null
+++ b/content/browser/websockets/websocket_handshake_request_info_impl.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 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/websockets/websocket_handshake_request_info_impl.h"
+
+#include "net/url_request/url_request.h"
+
+namespace content {
+
+namespace {
+
+constexpr int g_tag = 0;
+
+}  // namesapce
+
+WebSocketHandshakeRequestInfoImpl::WebSocketHandshakeRequestInfoImpl(
+    int child_id,
+    int render_frame_id)
+    : child_id_(child_id), render_frame_id_(render_frame_id) {}
+
+WebSocketHandshakeRequestInfoImpl::~WebSocketHandshakeRequestInfoImpl() {}
+
+void WebSocketHandshakeRequestInfoImpl::CreateInfoAndAssociateWithRequest(
+    int child_id,
+    int render_frame_id,
+    net::URLRequest* request) {
+  request->SetUserData(
+      &g_tag, new WebSocketHandshakeRequestInfoImpl(child_id, render_frame_id));
+}
+
+int WebSocketHandshakeRequestInfoImpl::GetChildId() const {
+  return child_id_;
+}
+
+int WebSocketHandshakeRequestInfoImpl::GetRenderFrameId() const {
+  return render_frame_id_;
+}
+
+const WebSocketHandshakeRequestInfo* WebSocketHandshakeRequestInfo::ForRequest(
+    const net::URLRequest* request) {
+  return static_cast<WebSocketHandshakeRequestInfoImpl*>(
+      request->GetUserData(&g_tag));
+}
+
+}  // namespace content
diff --git a/content/browser/websockets/websocket_handshake_request_info_impl.h b/content/browser/websockets/websocket_handshake_request_info_impl.h
new file mode 100644
index 0000000..4e768b1
--- /dev/null
+++ b/content/browser/websockets/websocket_handshake_request_info_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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.
+
+#ifndef CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
+#define CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
+
+#include "content/public/browser/websocket_handshake_request_info.h"
+
+#include "base/macros.h"
+#include "base/supports_user_data.h"
+
+namespace content {
+
+class WebSocketHandshakeRequestInfoImpl final
+    : public WebSocketHandshakeRequestInfo,
+      public base::SupportsUserData::Data {
+ public:
+  static void CreateInfoAndAssociateWithRequest(int child_id,
+                                                int render_frame_id,
+                                                net::URLRequest* request);
+
+  int GetChildId() const override;
+  int GetRenderFrameId() const override;
+
+ private:
+  WebSocketHandshakeRequestInfoImpl(int child_id, int render_frame_id);
+  ~WebSocketHandshakeRequestInfoImpl() override;
+
+  const int child_id_;
+  const int render_frame_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestInfoImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_WEBSOCKET_WEBSOCKET_HANDSHAKE_REQUEST_INFO_IMPL_H_
diff --git a/content/browser/websockets/websocket_impl.cc b/content/browser/websockets/websocket_impl.cc
index 6ea179d..7019bc5 100644
--- a/content/browser/websockets/websocket_impl.cc
+++ b/content/browser/websockets/websocket_impl.cc
@@ -22,6 +22,7 @@
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/ssl/ssl_error_handler.h"
 #include "content/browser/ssl/ssl_manager.h"
+#include "content/browser/websockets/websocket_handshake_request_info_impl.h"
 #include "content/public/browser/storage_partition.h"
 #include "ipc/ipc_message.h"
 #include "net/base/io_buffer.h"
@@ -90,6 +91,7 @@
 
   // net::WebSocketEventInterface implementation
 
+  void OnCreateURLRequest(net::URLRequest* url_request) override;
   ChannelState OnAddChannelResponse(const std::string& selected_subprotocol,
                                     const std::string& extensions) override;
   ChannelState OnDataFrame(bool fin,
@@ -151,6 +153,12 @@
            << reinterpret_cast<void*>(this);
 }
 
+void WebSocketImpl::WebSocketEventHandler::OnCreateURLRequest(
+    net::URLRequest* url_request) {
+  WebSocketHandshakeRequestInfoImpl::CreateInfoAndAssociateWithRequest(
+      impl_->child_id_, impl_->frame_id_, url_request);
+}
+
 ChannelState WebSocketImpl::WebSocketEventHandler::OnAddChannelResponse(
     const std::string& selected_protocol,
     const std::string& extensions) {
@@ -354,15 +362,16 @@
   callbacks_->ContinueSSLRequest();
 }
 
-WebSocketImpl::WebSocketImpl(
-    Delegate* delegate,
-    blink::mojom::WebSocketRequest request,
-    int frame_id,
-    base::TimeDelta delay)
+WebSocketImpl::WebSocketImpl(Delegate* delegate,
+                             blink::mojom::WebSocketRequest request,
+                             int child_id,
+                             int frame_id,
+                             base::TimeDelta delay)
     : delegate_(delegate),
       binding_(this, std::move(request)),
       delay_(delay),
       pending_flow_control_quota_(0),
+      child_id_(child_id),
       frame_id_(frame_id),
       handshake_succeeded_(false),
       weak_ptr_factory_(this) {
diff --git a/content/browser/websockets/websocket_impl.h b/content/browser/websockets/websocket_impl.h
index fb64613..c6da1316 100644
--- a/content/browser/websockets/websocket_impl.h
+++ b/content/browser/websockets/websocket_impl.h
@@ -46,6 +46,7 @@
 
   WebSocketImpl(Delegate* delegate,
                 blink::mojom::WebSocketRequest request,
+                int child_id,
                 int frame_id,
                 base::TimeDelta delay);
   ~WebSocketImpl() override;
@@ -96,6 +97,7 @@
   // Zero indicates there is no pending SendFlowControl().
   int64_t pending_flow_control_quota_;
 
+  int child_id_;
   int frame_id_;
 
   // handshake_succeeded_ is set and used by WebSocketManager to manage
diff --git a/content/browser/websockets/websocket_manager.cc b/content/browser/websockets/websocket_manager.cc
index bd0e372..6984564f 100644
--- a/content/browser/websockets/websocket_manager.cc
+++ b/content/browser/websockets/websocket_manager.cc
@@ -118,8 +118,8 @@
   // Keep all WebSocketImpls alive until either the client drops its
   // connection (see OnLostConnectionToClient) or we need to shutdown.
 
-  impls_.insert(CreateWebSocketImpl(this, std::move(request), frame_id,
-                                    CalculateDelay()));
+  impls_.insert(CreateWebSocketImpl(this, std::move(request), process_id_,
+                                    frame_id, CalculateDelay()));
   ++num_pending_connections_;
 
   if (!throttling_period_timer_.IsRunning()) {
@@ -161,9 +161,11 @@
 WebSocketImpl* WebSocketManager::CreateWebSocketImpl(
     WebSocketImpl::Delegate* delegate,
     blink::mojom::WebSocketRequest request,
+    int child_id,
     int frame_id,
     base::TimeDelta delay) {
-  return new WebSocketImpl(delegate, std::move(request), frame_id, delay);
+  return new WebSocketImpl(delegate, std::move(request), child_id, frame_id,
+                           delay);
 }
 
 int WebSocketManager::GetClientProcessId() {
diff --git a/content/browser/websockets/websocket_manager.h b/content/browser/websockets/websocket_manager.h
index bee498a..20132ded 100644
--- a/content/browser/websockets/websocket_manager.h
+++ b/content/browser/websockets/websocket_manager.h
@@ -45,6 +45,7 @@
   virtual WebSocketImpl* CreateWebSocketImpl(
       WebSocketImpl::Delegate* delegate,
       blink::mojom::WebSocketRequest request,
+      int child_id,
       int frame_id,
       base::TimeDelta delay);
 
diff --git a/content/browser/websockets/websocket_manager_unittest.cc b/content/browser/websockets/websocket_manager_unittest.cc
index c2a90b80..22cb7c4d 100644
--- a/content/browser/websockets/websocket_manager_unittest.cc
+++ b/content/browser/websockets/websocket_manager_unittest.cc
@@ -23,9 +23,14 @@
  public:
   TestWebSocketImpl(Delegate* delegate,
                     blink::mojom::WebSocketRequest request,
+                    int process_id,
                     int frame_id,
                     base::TimeDelta delay)
-      : WebSocketImpl(delegate, std::move(request), frame_id, delay) {}
+      : WebSocketImpl(delegate,
+                      std::move(request),
+                      process_id,
+                      frame_id,
+                      delay) {}
 
   base::TimeDelta delay() const { return delay_; }
 
@@ -61,10 +66,11 @@
  private:
   WebSocketImpl* CreateWebSocketImpl(WebSocketImpl::Delegate* delegate,
                                      blink::mojom::WebSocketRequest request,
+                                     int process_id,
                                      int frame_id,
                                      base::TimeDelta delay) override {
-    TestWebSocketImpl* impl =
-        new TestWebSocketImpl(delegate, std::move(request), frame_id, delay);
+    TestWebSocketImpl* impl = new TestWebSocketImpl(
+        delegate, std::move(request), process_id, frame_id, delay);
     // We keep a vector of sockets here to track their creation order.
     sockets_.push_back(impl);
     return impl;
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 3cf7963..143e737 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -273,6 +273,7 @@
     "web_ui_controller_factory.h",
     "web_ui_data_source.h",
     "web_ui_message_handler.h",
+    "websocket_handshake_request_info.h",
     "worker_service.h",
     "worker_service_observer.h",
     "zoom_level_delegate.h",
diff --git a/content/public/browser/websocket_handshake_request_info.h b/content/public/browser/websocket_handshake_request_info.h
new file mode 100644
index 0000000..def71272
--- /dev/null
+++ b/content/public/browser/websocket_handshake_request_info.h
@@ -0,0 +1,34 @@
+// Copyright 2016 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.
+
+#ifndef CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
+#define CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
+
+#include "content/common/content_export.h"
+
+namespace net {
+class URLRequest;
+}  // namespace net
+
+namespace content {
+
+// WebSocketHandshakeRequestInfo provides additional information attached to
+// a net::URLRequest.
+class WebSocketHandshakeRequestInfo {
+ public:
+  virtual ~WebSocketHandshakeRequestInfo() {}
+  // Returns the ID of the child process where the WebSocket connection lives.
+  virtual int GetChildId() const = 0;
+  // Returns the ID of the renderer frame where the WebSocket connection lives.
+  virtual int GetRenderFrameId() const = 0;
+
+  // Returns the WebSocketHandshakeRequestInfo instance attached to the given
+  // URLRequest. Returns nullptr when no instance is attached.
+  CONTENT_EXPORT static const WebSocketHandshakeRequestInfo* ForRequest(
+      const net::URLRequest* request);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_WEBSOCKET_HANDSHAKE_REQUEST_INFO_H_
diff --git a/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java b/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
index 9023991..5ca9b52 100644
--- a/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
+++ b/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
@@ -9,6 +9,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import org.apache.http.Header;
 import org.apache.http.HttpException;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
@@ -33,8 +34,10 @@
 import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.charset.Charset;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -80,13 +83,15 @@
         final Runnable mResponseAction;
         final boolean mIsNotFound;
         final boolean mIsNoContent;
+        final boolean mForWebSocket;
 
         Response(byte[] responseData, List<Pair<String, String>> responseHeaders,
-                boolean isRedirect, boolean isNotFound, boolean isNoContent,
+                boolean isRedirect, boolean isNotFound, boolean isNoContent, boolean forWebSocket,
                 Runnable responseAction) {
             mIsRedirect = isRedirect;
             mIsNotFound = isNotFound;
             mIsNoContent = isNoContent;
+            mForWebSocket = forWebSocket;
             mResponseData = responseData;
             mResponseHeaders = responseHeaders == null
                     ? new ArrayList<Pair<String, String>>() : responseHeaders;
@@ -195,6 +200,7 @@
     private static final int RESPONSE_STATUS_MOVED_TEMPORARILY = 1;
     private static final int RESPONSE_STATUS_NOT_FOUND = 2;
     private static final int RESPONSE_STATUS_NO_CONTENT = 3;
+    private static final int RESPONSE_STATUS_FOR_WEBSOCKET = 4;
 
     private String setResponseInternal(
             String requestPath, byte[] responseData,
@@ -203,11 +209,12 @@
         final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY);
         final boolean isNotFound = (status == RESPONSE_STATUS_NOT_FOUND);
         final boolean isNoContent = (status == RESPONSE_STATUS_NO_CONTENT);
+        final boolean forWebSocket = (status == RESPONSE_STATUS_FOR_WEBSOCKET);
 
         synchronized (mLock) {
-            mResponseMap.put(requestPath, new Response(
-                    responseData, responseHeaders, isRedirect, isNotFound, isNoContent,
-                    responseAction));
+            mResponseMap.put(
+                    requestPath, new Response(responseData, responseHeaders, isRedirect, isNotFound,
+                                         isNoContent, forWebSocket, responseAction));
             mResponseCountMap.put(requestPath, Integer.valueOf(0));
             mLastRequestMap.put(requestPath, null);
         }
@@ -344,6 +351,28 @@
     }
 
     /**
+     * Sets a response to a WebSocket handshake request.
+     *
+     * @param requestPath The path to respond to.
+     * @param responseHeaders Any additional headers that should be returned along with the
+     *                        response (null is acceptable).
+     * @return The full URL including the path that should be requested to get the expected
+     *         response.
+     */
+    public String setResponseForWebSocket(
+            String requestPath, List<Pair<String, String>> responseHeaders) {
+        if (responseHeaders == null) {
+            responseHeaders = new ArrayList<Pair<String, String>>();
+        } else {
+            responseHeaders = new ArrayList<Pair<String, String>>(responseHeaders);
+        }
+        responseHeaders.add(Pair.create("Connection", "Upgrade"));
+        responseHeaders.add(Pair.create("Upgrade", "websocket"));
+        return setResponseInternal(
+                requestPath, "".getBytes(), responseHeaders, null, RESPONSE_STATUS_FOR_WEBSOCKET);
+    }
+
+    /**
      * Get the number of requests was made at this path since it was last set.
      */
     public int getRequestCount(String requestPath) {
@@ -481,6 +510,23 @@
                 httpResponse.addHeader(header.first, header.second);
             }
             servedResponseFor(path, request);
+        } else if (response.mForWebSocket) {
+            Header[] keys = request.getHeaders("Sec-WebSocket-Key");
+            try {
+                if (keys.length == 1) {
+                    final String key = keys[0].getValue();
+                    httpResponse = createResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
+                    for (Pair<String, String> header : response.mResponseHeaders) {
+                        httpResponse.addHeader(header.first, header.second);
+                    }
+                    httpResponse.addHeader("Sec-WebSocket-Accept", computeWebSocketAccept(key));
+                } else {
+                    httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (NoSuchAlgorithmException e) {
+                httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
+            }
+            servedResponseFor(path, request);
         } else {
             if (response.mResponseAction != null) response.mResponseAction.run();
 
@@ -552,6 +598,20 @@
         return entity;
     }
 
+    /**
+     * Return a response for WebSocket handshake challenge.
+     */
+    private static String computeWebSocketAccept(String keyString) throws NoSuchAlgorithmException {
+        byte[] key = keyString.getBytes(Charset.forName("US-ASCII"));
+        byte[] guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(Charset.forName("US-ASCII"));
+
+        MessageDigest md = MessageDigest.getInstance("SHA");
+        md.update(key);
+        md.update(guid);
+        byte[] output = md.digest();
+        return Base64.encodeToString(output, Base64.DEFAULT);
+    }
+
     private static class ServerThread extends Thread {
         private TestWebServer mServer;
         private ServerSocket mSocket;
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
index 6cbd8b59..f5d639f 100644
--- a/net/websockets/websocket_channel.cc
+++ b/net/websockets/websocket_channel.cc
@@ -175,6 +175,10 @@
  public:
   explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
 
+  void OnCreateRequest(net::URLRequest* request) override {
+    creator_->OnCreateURLRequest(request);
+  }
+
   void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {
     creator_->OnConnectSuccess(std::move(stream));
     // |this| may have been deleted.
@@ -603,6 +607,10 @@
   SetState(CONNECTING);
 }
 
+void WebSocketChannel::OnCreateURLRequest(URLRequest* request) {
+  event_interface_->OnCreateURLRequest(request);
+}
+
 void WebSocketChannel::OnConnectSuccess(
     std::unique_ptr<WebSocketStream> stream) {
   DCHECK(stream);
diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h
index 43089791..2f68b14 100644
--- a/net/websockets/websocket_channel.h
+++ b/net/websockets/websocket_channel.h
@@ -33,6 +33,7 @@
 
 class NetLogWithSource;
 class IOBuffer;
+class URLRequest;
 class URLRequestContext;
 struct WebSocketHandshakeRequestInfo;
 struct WebSocketHandshakeResponseInfo;
@@ -226,6 +227,9 @@
       const std::string& additional_headers,
       const WebSocketStreamRequestCreationCallback& callback);
 
+  // Called when a URLRequest is created for handshaking.
+  void OnCreateURLRequest(URLRequest* request);
+
   // Success callback from WebSocketStream::CreateAndConnectStream(). Reports
   // success to the event interface. May delete |this|.
   void OnConnectSuccess(std::unique_ptr<WebSocketStream> stream);
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index 10ea232e..d2c477a 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -167,6 +167,7 @@
                              std::vector<char>(data, data + buffer_size));
   }
 
+  MOCK_METHOD1(OnCreateURLRequest, void(URLRequest*));
   MOCK_METHOD2(OnAddChannelResponse,
                ChannelState(const std::string&,
                             const std::string&));  // NOLINT
@@ -211,6 +212,7 @@
 // This fake EventInterface is for tests which need a WebSocketEventInterface
 // implementation but are not verifying how it is used.
 class FakeWebSocketEventInterface : public WebSocketEventInterface {
+  void OnCreateURLRequest(URLRequest* request) override {}
   ChannelState OnAddChannelResponse(const std::string& selected_protocol,
                                     const std::string& extensions) override {
     return CHANNEL_ALIVE;
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 31b557a..ba2a657 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -37,6 +37,8 @@
 
 namespace net {
 
+class URLRequest;
+
 namespace {
 
 static const char kEchoServer[] = "echo-with-no-extension";
@@ -66,6 +68,8 @@
   std::string extensions() const;
 
   // Implementation of WebSocketEventInterface.
+  void OnCreateURLRequest(URLRequest* request) override {}
+
   ChannelState OnAddChannelResponse(const std::string& selected_subprotocol,
                                     const std::string& extensions) override;
 
diff --git a/net/websockets/websocket_event_interface.h b/net/websockets/websocket_event_interface.h
index ac29659..db24207f 100644
--- a/net/websockets/websocket_event_interface.h
+++ b/net/websockets/websocket_event_interface.h
@@ -22,6 +22,7 @@
 
 class IOBuffer;
 class SSLInfo;
+class URLRequest;
 struct WebSocketHandshakeRequestInfo;
 struct WebSocketHandshakeResponseInfo;
 
@@ -41,6 +42,9 @@
 
   virtual ~WebSocketEventInterface() {}
 
+  // Called when a URLRequest is created for handshaking.
+  virtual void OnCreateURLRequest(URLRequest* request) = 0;
+
   // Called in response to an AddChannelRequest. This means that a response has
   // been received from the remote server.
   virtual ChannelState OnAddChannelResponse(
diff --git a/net/websockets/websocket_handshake_stream_create_helper_test.cc b/net/websockets/websocket_handshake_stream_create_helper_test.cc
index 6b43527ff..f4d7c26 100644
--- a/net/websockets/websocket_handshake_stream_create_helper_test.cc
+++ b/net/websockets/websocket_handshake_stream_create_helper_test.cc
@@ -65,6 +65,7 @@
  public:
   ~TestConnectDelegate() override {}
 
+  void OnCreateRequest(URLRequest* request) override {}
   void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {}
   void OnFailure(const std::string& failure_message) override {}
   void OnStartOpeningHandshake(
diff --git a/net/websockets/websocket_stream.cc b/net/websockets/websocket_stream.cc
index ef62c24..8cca365 100644
--- a/net/websockets/websocket_stream.cc
+++ b/net/websockets/websocket_stream.cc
@@ -116,6 +116,7 @@
         WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
         handshake_stream_create_helper_);
     url_request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE);
+    connect_delegate_->OnCreateRequest(url_request_.get());
   }
 
   // Destroying this object destroys the URLRequest, which cancels the request
diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h
index 389e092..e960f3d 100644
--- a/net/websockets/websocket_stream.h
+++ b/net/websockets/websocket_stream.h
@@ -32,6 +32,7 @@
 namespace net {
 
 class NetLogWithSource;
+class URLRequest;
 class URLRequestContext;
 struct WebSocketFrame;
 class WebSocketHandshakeStreamBase;
@@ -71,6 +72,9 @@
   class NET_EXPORT_PRIVATE ConnectDelegate {
    public:
     virtual ~ConnectDelegate();
+    // Called when the URLRequest is created.
+    virtual void OnCreateRequest(URLRequest* url_request) = 0;
+
     // Called on successful connection. The parameter is an object derived from
     // WebSocketStream.
     virtual void OnSuccess(std::unique_ptr<WebSocketStream> stream) = 0;
diff --git a/net/websockets/websocket_stream_create_test_base.cc b/net/websockets/websocket_stream_create_test_base.cc
index 6ca5d70..d3b1668 100644
--- a/net/websockets/websocket_stream_create_test_base.cc
+++ b/net/websockets/websocket_stream_create_test_base.cc
@@ -50,6 +50,10 @@
                       const base::Closure& done_callback)
       : owner_(owner), done_callback_(done_callback) {}
 
+  void OnCreateRequest(URLRequest* request) override {
+    owner_->url_request_ = request;
+  }
+
   void OnSuccess(std::unique_ptr<WebSocketStream> stream) override {
     stream.swap(owner_->stream_);
     done_callback_.Run();
@@ -92,8 +96,7 @@
 };
 
 WebSocketStreamCreateTestBase::WebSocketStreamCreateTestBase()
-    : has_failed_(false), ssl_fatal_(false) {
-}
+    : has_failed_(false), ssl_fatal_(false), url_request_(nullptr) {}
 
 WebSocketStreamCreateTestBase::~WebSocketStreamCreateTestBase() {
 }
diff --git a/net/websockets/websocket_stream_create_test_base.h b/net/websockets/websocket_stream_create_test_base.h
index be7912f..5805b50 100644
--- a/net/websockets/websocket_stream_create_test_base.h
+++ b/net/websockets/websocket_stream_create_test_base.h
@@ -24,6 +24,7 @@
 
 class HttpRequestHeaders;
 class HttpResponseHeaders;
+class URLRequest;
 class WebSocketStream;
 class WebSocketStreamRequest;
 struct WebSocketHandshakeRequestInfo;
@@ -75,6 +76,7 @@
   SSLInfo ssl_info_;
   bool ssl_fatal_;
   std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_data_;
+  URLRequest* url_request_;
 
   // This temporarily sets WebSocketEndpointLockManager unlock delay to zero
   // during tests.
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc
index fcafedc..091c28d 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -325,11 +325,13 @@
 
 // Confirm that the basic case works as expected.
 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
+  EXPECT_FALSE(url_request_);
   CreateAndConnectStandard("ws://localhost/", "localhost", "/",
                            NoSubProtocols(), LocalhostOrigin(), LocalhostUrl(),
                            "", "", "");
   EXPECT_FALSE(request_info_);
   EXPECT_FALSE(response_info_);
+  EXPECT_TRUE(url_request_);
   WaitUntilConnectDone();
   EXPECT_FALSE(has_failed());
   EXPECT_TRUE(stream_);