bug 723628 - speculative connect hint interface r=honzab sr=biesi
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 25 Apr 2012 08:54:42 -0400
changeset 92419 57f7b1c6ae5075a7300832904c24e0de07b32ca7
parent 92418 c773ee0f0245aa84b7fe12b95d6664c2ba27888e
child 92420 dc7ccdd7361052b5fdf8b6d4e1ff33aa6860c53c
push id22531
push userkhuey@mozilla.com
push dateThu, 26 Apr 2012 03:23:06 +0000
treeherdermozilla-central@b893f852fe7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab, biesi
bugs723628
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 723628 - speculative connect hint interface r=honzab sr=biesi
netwerk/base/public/Makefile.in
netwerk/base/public/nsISpeculativeConnect.idl
netwerk/base/src/nsIOService.cpp
netwerk/base/src/nsIOService.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/test/unit/test_speculative_connect.js
netwerk/test/unit/xpcshell.ini
--- a/netwerk/base/public/Makefile.in
+++ b/netwerk/base/public/Makefile.in
@@ -98,16 +98,17 @@ XPIDLSRCS	= \
 		nsIProtocolProxyFilter.idl \
 		nsIProtocolProxyCallback.idl \
 		nsIProxiedProtocolHandler.idl \
 		nsIProxyAutoConfig.idl \
 		nsIProxyInfo.idl \
 		nsITransport.idl \
 		nsISocketTransport.idl \
 		nsISocketTransportService.idl \
+		nsISpeculativeConnect.idl \
 		nsIServerSocket.idl \
 		nsIResumableChannel.idl \
 		nsIRequestObserverProxy.idl \
 		nsISecurityInfoProvider.idl \
 		nsIStreamListenerTee.idl \
 		nsISimpleStreamListener.idl \
 		nsIStreamTransportService.idl \
 		nsIStreamLoader.idl \
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsISpeculativeConnect.idl
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Patrick McManus <mcmanus@ducksong.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsIEventTarget;
+
+[scriptable, uuid(b3c53863-1313-480a-90a2-5b0da651ee5e)]
+interface nsISpeculativeConnect : nsISupports
+{
+    /**
+     * Called as a hint to indicate a new transaction for the URI is likely coming
+     * soon. The implementer may use this information to start a TCP
+     * and/or SSL level handshake for that resource immediately so that it is
+     * ready and/or progressed when the transaction is actually submitted.
+     *
+     * No obligation is taken on by the implementer, nor is the submitter obligated
+     * to actually open the new channel. 
+     *
+     * @param aURI the URI of the hinted transaction
+     * @param aCallbacks any security callbacks for use with SSL for interfaces
+     *        such as nsIBadCertListener. May be null.
+     * @param aTarget the thread on which the release of the callbacks will
+     *        occur. May be null for "any thread".
+     *
+     */
+    void speculativeConnect(in nsIURI aURI,
+                            in nsIInterfaceRequestor aCallbacks,
+                            in nsIEventTarget aTarget);
+
+};
+
--- a/netwerk/base/src/nsIOService.cpp
+++ b/netwerk/base/src/nsIOService.cpp
@@ -336,20 +336,21 @@ nsIOService::GetInstance() {
             return nsnull;
         }
         return gIOService;
     }
     NS_ADDREF(gIOService);
     return gIOService;
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS5(nsIOService,
+NS_IMPL_THREADSAFE_ISUPPORTS6(nsIOService,
                               nsIIOService,
                               nsIIOService2,
                               nsINetUtil,
+                              nsISpeculativeConnect,
                               nsIObserver,
                               nsISupportsWeakReference)
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 nsIOService::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
                                     PRUint32 flags,
@@ -603,20 +604,57 @@ nsIOService::NewFileURI(nsIFile *file, n
 }
 
 NS_IMETHODIMP
 nsIOService::NewChannelFromURI(nsIURI *aURI, nsIChannel **result)
 {
     return NewChannelFromURIWithProxyFlags(aURI, nsnull, 0, result);
 }
 
+void
+nsIOService::LookupProxyInfo(nsIURI *aURI,
+                             nsIURI *aProxyURI,
+                             PRUint32 aProxyFlags,
+                             nsCString *aScheme,
+                             nsIProxyInfo **outPI)
+{
+    nsresult rv;
+    nsCOMPtr<nsIProxyInfo> pi;
+
+    if (!mProxyService) {
+        mProxyService = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+        if (!mProxyService)
+            NS_WARNING("failed to get protocol proxy service");
+    }
+    if (mProxyService) {
+        PRUint32 flags = 0;
+        if (aScheme->EqualsLiteral("http") || aScheme->EqualsLiteral("https"))
+            flags = nsIProtocolProxyService::RESOLVE_NON_BLOCKING;
+        rv = mProxyService->Resolve(aProxyURI ? aProxyURI : aURI, aProxyFlags,
+                                    getter_AddRefs(pi));
+        if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+            // Use an UNKNOWN proxy to defer resolution and avoid blocking.
+            rv = mProxyService->NewProxyInfo(NS_LITERAL_CSTRING("unknown"),
+                                             NS_LITERAL_CSTRING(""),
+                                             -1, 0, 0, nsnull,
+                                             getter_AddRefs(pi));
+        }
+        if (NS_FAILED(rv))
+            pi = nsnull;
+    }
+    *outPI = pi;
+    if (pi)
+        pi.forget();
+}
+
+
 NS_IMETHODIMP
 nsIOService::NewChannelFromURIWithProxyFlags(nsIURI *aURI,
                                              nsIURI *aProxyURI,
-                                             PRUint32 proxyFlags,
+                                             PRUint32 aProxyFlags,
                                              nsIChannel **result)
 {
     nsresult rv;
     NS_ENSURE_ARG_POINTER(aURI);
 
     nsCAutoString scheme;
     rv = aURI->GetScheme(scheme);
     if (NS_FAILED(rv))
@@ -631,37 +669,17 @@ nsIOService::NewChannelFromURIWithProxyF
     rv = handler->GetProtocolFlags(&protoFlags);
     if (NS_FAILED(rv))
         return rv;
 
     // Talk to the PPS if the protocol handler allows proxying.  Otherwise,
     // skip this step.  This allows us to lazily load the PPS at startup.
     if (protoFlags & nsIProtocolHandler::ALLOWS_PROXY) {
         nsCOMPtr<nsIProxyInfo> pi;
-        if (!mProxyService) {
-            mProxyService = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
-            if (!mProxyService)
-                NS_WARNING("failed to get protocol proxy service");
-        }
-        if (mProxyService) {
-            PRUint32 flags = 0;
-            if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https"))
-                flags = nsIProtocolProxyService::RESOLVE_NON_BLOCKING;
-            rv = mProxyService->Resolve(aProxyURI ? aProxyURI : aURI, proxyFlags,
-                                        getter_AddRefs(pi));
-            if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
-                // Use an UNKNOWN proxy to defer resolution and avoid blocking.
-                rv = mProxyService->NewProxyInfo(NS_LITERAL_CSTRING("unknown"),
-                                                 NS_LITERAL_CSTRING(""),
-                                                 -1, 0, 0, nsnull,
-                                                 getter_AddRefs(pi));
-            }
-            if (NS_FAILED(rv))
-                pi = nsnull;
-        }
+        LookupProxyInfo(aURI, aProxyURI, aProxyFlags, &scheme, getter_AddRefs(pi));
         if (pi) {
             nsCAutoString type;
             if (NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
                 // we are going to proxy this channel using an http proxy
                 rv = GetProtocolHandler("http", getter_AddRefs(handler));
                 if (NS_FAILED(rv))
                     return rv;
             }
@@ -1231,8 +1249,42 @@ nsIOService::ExtractCharsetFromContentTy
     nsCAutoString ignored;
     net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
                          aCharsetStart, aCharsetEnd);
     if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
         *aHadCharset = false;
     }
     return NS_OK;
 }
+
+// nsISpeculativeConnect
+NS_IMETHODIMP
+nsIOService::SpeculativeConnect(nsIURI *aURI,
+                                nsIInterfaceRequestor *aCallbacks,
+                                nsIEventTarget *aTarget)
+{
+    nsCAutoString scheme;
+    nsresult rv = aURI->GetScheme(scheme);
+    if (NS_FAILED(rv))
+        return rv;
+
+    // Check for proxy information. If there is a proxy configured then a
+    // speculative connect should not be performed because the potential
+    // reward is slim with tcp peers closely located to the browser.
+    nsCOMPtr<nsIProxyInfo> pi;
+    LookupProxyInfo(aURI, nsnull, 0, &scheme, getter_AddRefs(pi));
+    if (pi) 
+        return NS_OK;
+
+    nsCOMPtr<nsIProtocolHandler> handler;
+    rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+        do_QueryInterface(handler);
+    if (!handler)
+        return NS_OK;
+
+    return speculativeHandler->SpeculativeConnect(aURI,
+                                                  aCallbacks,
+                                                  aTarget);
+}
--- a/netwerk/base/src/nsIOService.h
+++ b/netwerk/base/src/nsIOService.h
@@ -53,40 +53,43 @@
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsINetUtil.h"
 #include "nsIChannelEventSink.h"
 #include "nsIContentSniffer.h"
 #include "nsCategoryCache.h"
 #include "nsINetworkLinkService.h"
 #include "nsAsyncRedirectVerifyHelper.h"
+#include "nsISpeculativeConnect.h"
 
 #define NS_N(x) (sizeof(x)/sizeof(*x))
 
 // We don't want to expose this observer topic.
 // Intended internal use only for remoting offline/inline events.
 // See Bug 552829
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 
 static const char gScheme[][sizeof("resource")] =
     {"chrome", "file", "http", "jar", "resource"};
 
 class nsIPrefBranch;
 
 class nsIOService : public nsIIOService2
                   , public nsIObserver
                   , public nsINetUtil
+                  , public nsISpeculativeConnect
                   , public nsSupportsWeakReference
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIIOSERVICE
     NS_DECL_NSIIOSERVICE2
     NS_DECL_NSIOBSERVER
     NS_DECL_NSINETUTIL
+    NS_DECL_NSISPECULATIVECONNECT
 
     // Gets the singleton instance of the IO Service, creating it as needed
     // Returns nsnull on out of memory or failure to initialize.
     // Returns an addrefed pointer.
     static nsIOService* GetInstance();
 
     NS_HIDDEN_(nsresult) Init();
     NS_HIDDEN_(nsresult) NewURI(const char* aSpec, nsIURI* aBaseURI,
@@ -130,16 +133,20 @@ private:
     // Prefs wrangling
     NS_HIDDEN_(void) PrefsChanged(nsIPrefBranch *prefs, const char *pref = nsnull);
     NS_HIDDEN_(void) GetPrefBranch(nsIPrefBranch **);
     NS_HIDDEN_(void) ParsePortList(nsIPrefBranch *prefBranch, const char *pref, bool remove);
 
     nsresult InitializeSocketTransportService();
     nsresult InitializeNetworkLinkService();
 
+    // consolidated helper function
+    void LookupProxyInfo(nsIURI *aURI, nsIURI *aProxyURI, PRUint32 aProxyFlags,
+                         nsCString *aScheme, nsIProxyInfo **outPI);
+
 private:
     bool                                 mOffline;
     bool                                 mOfflineForProfileChange;
     bool                                 mManageOfflineStatus;
 
     // Used to handle SetOffline() reentrancy.  See the comment in
     // SetOffline() for more details.
     bool                                 mSettingOffline;
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -1412,22 +1412,23 @@ nsHttpHandler::SetAcceptEncodings(const 
     mAcceptEncodings = aAcceptEncodings;
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpHandler::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_THREADSAFE_ISUPPORTS5(nsHttpHandler,
+NS_IMPL_THREADSAFE_ISUPPORTS6(nsHttpHandler,
                               nsIHttpProtocolHandler,
                               nsIProxiedProtocolHandler,
                               nsIProtocolHandler,
                               nsIObserver,
-                              nsISupportsWeakReference)
+                              nsISupportsWeakReference,
+                              nsISpeculativeConnect)
 
 //-----------------------------------------------------------------------------
 // nsHttpHandler::nsIProtocolHandler
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpHandler::GetScheme(nsACString &aScheme)
 {
@@ -1664,25 +1665,85 @@ nsHttpHandler::Observe(nsISupports *subj
         nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
         if (uri && mConnMgr)
             mConnMgr->ReportFailedToProcess(uri);
     }
   
     return NS_OK;
 }
 
+// nsISpeculativeConnect
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
+                                  nsIInterfaceRequestor *aCallbacks,
+                                  nsIEventTarget *aTarget)
+{
+    nsIStrictTransportSecurityService* stss = gHttpHandler->GetSTSService();
+    bool isStsHost = false;
+    if (!stss)
+        return NS_OK;
+    
+    nsCOMPtr<nsIURI> clone;
+    if (NS_SUCCEEDED(stss->IsStsURI(aURI, &isStsHost)) && isStsHost) {
+        if (NS_SUCCEEDED(aURI->Clone(getter_AddRefs(clone)))) {
+            clone->SetScheme(NS_LITERAL_CSTRING("https"));
+            aURI = clone.get();
+        }
+    }
+
+    nsCAutoString scheme;
+    nsresult rv = aURI->GetScheme(scheme);
+    if (NS_FAILED(rv))
+        return rv;
+
+    // If this is HTTPS, make sure PSM is initialized as the channel
+    // creation path may have been bypassed
+    if (scheme.EqualsLiteral("https")) {
+        if (!IsNeckoChild()) {
+            // make sure PSM gets initialized on the main thread.
+            net_EnsurePSMInit();
+        }
+    }
+    // Ensure that this is HTTP or HTTPS, otherwise we don't do preconnect here
+    else if (!scheme.EqualsLiteral("http"))
+        return NS_ERROR_UNEXPECTED;
+
+    // Construct connection info object
+    bool usingSSL = false;
+    rv = aURI->SchemeIs("https", &usingSSL);
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsCAutoString host;
+    rv = aURI->GetAsciiHost(host);
+    if (NS_FAILED(rv))
+        return rv;
+
+    PRInt32 port = -1;
+    rv = aURI->GetPort(&port);
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsHttpConnectionInfo *ci =
+        new nsHttpConnectionInfo(host, port, nsnull, usingSSL);
+
+    return SpeculativeConnect(ci, aCallbacks, aTarget);
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpsHandler implementation
 //-----------------------------------------------------------------------------
 
-NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpsHandler,
+NS_IMPL_THREADSAFE_ISUPPORTS5(nsHttpsHandler,
                               nsIHttpProtocolHandler,
                               nsIProxiedProtocolHandler,
                               nsIProtocolHandler,
-                              nsISupportsWeakReference)
+                              nsISupportsWeakReference,
+                              nsISpeculativeConnect)
 
 nsresult
 nsHttpsHandler::Init()
 {
     nsCOMPtr<nsIProtocolHandler> httpHandler(
             do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http"));
     NS_ASSERTION(httpHandler.get() != nsnull, "no http handler?");
     return NS_OK;
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -56,38 +56,41 @@
 #include "nsIObserverService.h"
 #include "nsIPrivateBrowsingService.h"
 #include "nsIStreamConverterService.h"
 #include "nsICacheSession.h"
 #include "nsICookieService.h"
 #include "nsIIDNService.h"
 #include "nsITimer.h"
 #include "nsIStrictTransportSecurityService.h"
+#include "nsISpeculativeConnect.h"
 
 class nsHttpConnectionInfo;
 class nsHttpHeaderArray;
 class nsHttpTransaction;
 class nsAHttpTransaction;
 class nsIHttpChannel;
 class nsIPrefBranch;
 
 //-----------------------------------------------------------------------------
 // nsHttpHandler - protocol handler for HTTP and HTTPS
 //-----------------------------------------------------------------------------
 
 class nsHttpHandler : public nsIHttpProtocolHandler
                     , public nsIObserver
                     , public nsSupportsWeakReference
+                    , public nsISpeculativeConnect
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIPROTOCOLHANDLER
     NS_DECL_NSIPROXIEDPROTOCOLHANDLER
     NS_DECL_NSIHTTPPROTOCOLHANDLER
     NS_DECL_NSIOBSERVER
+    NS_DECL_NSISPECULATIVECONNECT
 
     nsHttpHandler();
     virtual ~nsHttpHandler();
 
     nsresult Init();
     nsresult AddStandardRequestHeaders(nsHttpHeaderArray *,
                                        PRUint8 capabilities,
                                        bool useProxy);
@@ -407,25 +410,27 @@ extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
 //                  HTTPS handler (even though they share the same impl).
 //-----------------------------------------------------------------------------
 
 class nsHttpsHandler : public nsIHttpProtocolHandler
                      , public nsSupportsWeakReference
+                     , public nsISpeculativeConnect
 {
 public:
     // we basically just want to override GetScheme and GetDefaultPort...
     // all other methods should be forwarded to the nsHttpHandler instance.
     
     NS_DECL_ISUPPORTS
     NS_DECL_NSIPROTOCOLHANDLER
     NS_FORWARD_NSIPROXIEDPROTOCOLHANDLER (gHttpHandler->)
     NS_FORWARD_NSIHTTPPROTOCOLHANDLER    (gHttpHandler->)
+    NS_FORWARD_NSISPECULATIVECONNECT     (gHttpHandler->)
 
     nsHttpsHandler() { }
     virtual ~nsHttpsHandler() { }
 
     nsresult Init();
 };
 
 #endif // nsHttpHandler_h__
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -0,0 +1,42 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const CC = Components.Constructor;
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+                        "nsIServerSocket",
+                        "init");
+
+var serv;
+
+function TestServer() {
+    this.listener = ServerSocket(4444, true, -1);
+    this.listener.asyncListen(this);
+}
+
+TestServer.prototype = {
+    QueryInterface: function(iid) {
+        if (iid.equals(Ci.nsIServerSocket) ||
+            iid.equals(Ci.nsISupports))
+            return this;
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+    },
+    onSocketAccepted: function(socket, trans) {
+        try { this.listener.close(); } catch(e) {}
+        do_check_true(true);
+        do_test_finished();
+    },
+
+    onStopListening: function(socket) {}
+}
+
+function run_test() {
+    var ios = Cc["@mozilla.org/network/io-service;1"]
+        .getService(Ci.nsIIOService);    
+
+    serv = new TestServer();
+    URI = ios.newURI("http://localhost:4444/just/a/test", null, null);
+    ios.QueryInterface(Components.interfaces.nsISpeculativeConnect)
+        .speculativeConnect(URI, null, null);
+    do_test_pending();
+}
+
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -169,16 +169,17 @@ skip-if = os == "win"
 [test_resumable_channel.js]
 [test_resumable_truncate.js]
 [test_safeoutputstream.js]
 [test_simple.js]
 [test_sockettransportsvc_available.js]
 [test_socks.js]
 # Bug 675039: test hangs consistently on Android
 skip-if = os == "android"
+[test_speculative_connect.js]
 [test_standardurl.js]
 [test_standardurl_port.js]
 [test_streamcopier.js]
 [test_traceable_channel.js]
 [test_unescapestring.js]
 [test_xmlhttprequest.js]
 [test_XHR_redirects.js]
 [test_pinned_app_cache.js]