diff --git a/netwerk/base/public/Makefile.in b/netwerk/base/public/Makefile.in index 4388267e7db..e635b51b15c 100644 --- a/netwerk/base/public/Makefile.in +++ b/netwerk/base/public/Makefile.in @@ -103,6 +103,7 @@ XPIDLSRCS = \ nsITransport.idl \ nsISocketTransport.idl \ nsISocketTransportService.idl \ + nsISpeculativeConnect.idl \ nsIServerSocket.idl \ nsIResumableChannel.idl \ nsIRequestObserverProxy.idl \ diff --git a/netwerk/base/public/nsISpeculativeConnect.idl b/netwerk/base/public/nsISpeculativeConnect.idl new file mode 100644 index 00000000000..ef12d4a9445 --- /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 + * + * 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); + +}; + diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp index 9ae146708d9..5197bf772e1 100644 --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -341,10 +341,11 @@ nsIOService::GetInstance() { return gIOService; } -NS_IMPL_THREADSAFE_ISUPPORTS5(nsIOService, +NS_IMPL_THREADSAFE_ISUPPORTS6(nsIOService, nsIIOService, nsIIOService2, nsINetUtil, + nsISpeculativeConnect, nsIObserver, nsISupportsWeakReference) @@ -608,10 +609,47 @@ 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 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; @@ -636,27 +674,7 @@ nsIOService::NewChannelFromURIWithProxyFlags(nsIURI *aURI, // skip this step. This allows us to lazily load the PPS at startup. if (protoFlags & nsIProtocolHandler::ALLOWS_PROXY) { nsCOMPtr 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")) { @@ -1236,3 +1254,37 @@ nsIOService::ExtractCharsetFromContentType(const nsACString &aTypeHeader, } 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 pi; + LookupProxyInfo(aURI, nsnull, 0, &scheme, getter_AddRefs(pi)); + if (pi) + return NS_OK; + + nsCOMPtr handler; + rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr speculativeHandler = + do_QueryInterface(handler); + if (!handler) + return NS_OK; + + return speculativeHandler->SpeculativeConnect(aURI, + aCallbacks, + aTarget); +} diff --git a/netwerk/base/src/nsIOService.h b/netwerk/base/src/nsIOService.h index fe5d63b924d..3ce343e03e4 100644 --- a/netwerk/base/src/nsIOService.h +++ b/netwerk/base/src/nsIOService.h @@ -58,6 +58,7 @@ #include "nsCategoryCache.h" #include "nsINetworkLinkService.h" #include "nsAsyncRedirectVerifyHelper.h" +#include "nsISpeculativeConnect.h" #define NS_N(x) (sizeof(x)/sizeof(*x)) @@ -74,6 +75,7 @@ class nsIPrefBranch; class nsIOService : public nsIIOService2 , public nsIObserver , public nsINetUtil + , public nsISpeculativeConnect , public nsSupportsWeakReference { public: @@ -82,6 +84,7 @@ public: 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. @@ -135,6 +138,10 @@ private: nsresult InitializeSocketTransportService(); nsresult InitializeNetworkLinkService(); + // consolidated helper function + void LookupProxyInfo(nsIURI *aURI, nsIURI *aProxyURI, PRUint32 aProxyFlags, + nsCString *aScheme, nsIProxyInfo **outPI); + private: bool mOffline; bool mOfflineForProfileChange; diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index 56bca0ec04d..91ef8b52a09 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -1417,12 +1417,13 @@ nsHttpHandler::SetAcceptEncodings(const char *aAcceptEncodings) // nsHttpHandler::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_THREADSAFE_ISUPPORTS5(nsHttpHandler, +NS_IMPL_THREADSAFE_ISUPPORTS6(nsHttpHandler, nsIHttpProtocolHandler, nsIProxiedProtocolHandler, nsIProtocolHandler, nsIObserver, - nsISupportsWeakReference) + nsISupportsWeakReference, + nsISpeculativeConnect) //----------------------------------------------------------------------------- // nsHttpHandler::nsIProtocolHandler @@ -1669,15 +1670,75 @@ nsHttpHandler::Observe(nsISupports *subject, 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 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() diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 68426da0d8a..f5e2d346f4a 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -61,6 +61,7 @@ #include "nsIIDNService.h" #include "nsITimer.h" #include "nsIStrictTransportSecurityService.h" +#include "nsISpeculativeConnect.h" class nsHttpConnectionInfo; class nsHttpHeaderArray; @@ -76,6 +77,7 @@ class nsIPrefBranch; class nsHttpHandler : public nsIHttpProtocolHandler , public nsIObserver , public nsSupportsWeakReference + , public nsISpeculativeConnect { public: NS_DECL_ISUPPORTS @@ -83,6 +85,7 @@ public: NS_DECL_NSIPROXIEDPROTOCOLHANDLER NS_DECL_NSIHTTPPROTOCOLHANDLER NS_DECL_NSIOBSERVER + NS_DECL_NSISPECULATIVECONNECT nsHttpHandler(); virtual ~nsHttpHandler(); @@ -412,6 +415,7 @@ extern nsHttpHandler *gHttpHandler; class nsHttpsHandler : public nsIHttpProtocolHandler , public nsSupportsWeakReference + , public nsISpeculativeConnect { public: // we basically just want to override GetScheme and GetDefaultPort... @@ -421,6 +425,7 @@ public: NS_DECL_NSIPROTOCOLHANDLER NS_FORWARD_NSIPROXIEDPROTOCOLHANDLER (gHttpHandler->) NS_FORWARD_NSIHTTPPROTOCOLHANDLER (gHttpHandler->) + NS_FORWARD_NSISPECULATIVECONNECT (gHttpHandler->) nsHttpsHandler() { } virtual ~nsHttpsHandler() { } diff --git a/netwerk/test/unit/test_speculative_connect.js b/netwerk/test/unit/test_speculative_connect.js new file mode 100644 index 00000000000..c7fda7b1ad5 --- /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(); +} + diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index fc1bbe047e8..36f0e6fe687 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -174,6 +174,7 @@ skip-if = os == "win" [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]