bug 1135160 - implement link rel=preconnect r=smaug
authorPatrick McManus <mcmanus@ducksong.com>
Fri, 20 Feb 2015 17:01:36 -0500
changeset 232872 020545d52adfa2efeaa4ce1d921afc5a918a44c5
parent 232871 cbfb5e4c944a3ba47a41f481ca97e7cfd904247e
child 232873 eb63da364f25cc1a48d8a9c2068cc747568851d3
push id28394
push userkwierso@gmail.com
push dateWed, 11 Mar 2015 01:24:00 +0000
treeherdermozilla-central@fd8e079d6335 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1135160
milestone39.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 1135160 - implement link rel=preconnect r=smaug
dom/base/nsContentSink.cpp
dom/base/nsContentSink.h
dom/base/nsStyleLinkElement.cpp
dom/base/nsStyleLinkElement.h
dom/html/HTMLLinkElement.cpp
dom/html/HTMLLinkElement.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/test/mochitests/mochitest.ini
netwerk/test/mochitests/rel_preconnect.sjs
netwerk/test/mochitests/test_rel_preconnect.html
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -28,16 +28,17 @@
 #include "nsIAtom.h"
 #include "nsGkAtoms.h"
 #include "nsNetCID.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsISpeculativeConnect.h"
 #include "nsICookieService.h"
 #include "nsContentUtils.h"
 #include "nsNodeInfoManager.h"
 #include "nsIAppShell.h"
 #include "nsIWidget.h"
 #include "nsWidgetsCID.h"
 #include "nsIDOMNode.h"
 #include "mozAutoDocUpdate.h"
@@ -681,16 +682,20 @@ nsContentSink::ProcessLink(const nsSubst
   if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
     PrefetchHref(aHref, mDocument, hasPrefetch);
   }
 
   if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) {
     PrefetchDNS(aHref);
   }
 
+  if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::ePRECONNECT)) {
+    Preconnect(aHref);
+  }
+
   // is it a stylesheet link?
   if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
     return NS_OK;
   }
 
   bool isAlternate = linkTypes & nsStyleLinkElement::eALTERNATE;
   return ProcessStyleLink(nullptr, aHref, isAlternate, aTitle, aType,
                           aMedia);
@@ -852,16 +857,36 @@ nsContentSink::PrefetchDNS(const nsAStri
     CopyUTF8toUTF16(host, hostname);
   }
 
   if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) {
     nsHTMLDNSPrefetch::PrefetchLow(hostname);
   }
 }
 
+void
+nsContentSink::Preconnect(const nsAString &aHref)
+{
+  nsCOMPtr<nsISpeculativeConnect>
+    speculator(do_QueryInterface(nsContentUtils::GetIOService()));
+  if (!speculator) {
+    return;
+  }
+
+  // construct URI using document charset
+  const nsACString& charset = mDocument->GetDocumentCharacterSet();
+  nsCOMPtr<nsIURI> uri;
+  NS_NewURI(getter_AddRefs(uri), aHref,
+            charset.IsEmpty() ? nullptr : PromiseFlatCString(charset).get(),
+            mDocument->GetDocBaseURI());
+  if (uri) {
+    speculator->SpeculativeConnect(uri, nullptr);
+  }
+}
+
 nsresult
 nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache,
                                  nsIURI *aManifestURI,
                                  bool aFetchedWithHTTPGetOrEquiv,
                                  CacheSelectionAction *aAction)
 {
   nsresult rv;
 
--- a/dom/base/nsContentSink.h
+++ b/dom/base/nsContentSink.h
@@ -159,19 +159,20 @@ protected:
                                     bool aAlternate,
                                     const nsSubstring& aTitle,
                                     const nsSubstring& aType,
                                     const nsSubstring& aMedia);
 
   void PrefetchHref(const nsAString &aHref, nsINode *aSource,
                     bool aExplicit);
 
-  // aHref can either be the usual URI format or of the form "//www.hostname.com"
-  // without a scheme.
+  // For both PrefetchDNS() and Preconnect() aHref can either be the usual
+  // URI format or of the form "//www.hostname.com" without a scheme.
   void PrefetchDNS(const nsAString &aHref);
+  void Preconnect(const nsAString &aHref);
 
   // Gets the cache key (used to identify items in a cache) of the channel.
   nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey);
 
   // There is an offline cache manifest attribute specified and the
   // document is allowed to use the offline cache.  Process the cache
   // selection algorithm for this document and the manifest. Result is
   // an action that must be taken on the manifest, see
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -145,16 +145,18 @@ static uint32_t ToLinkMask(const nsAStri
     return nsStyleLinkElement::eSTYLESHEET;
   else if (aLink.EqualsLiteral("next"))
     return nsStyleLinkElement::eNEXT;
   else if (aLink.EqualsLiteral("alternate"))
     return nsStyleLinkElement::eALTERNATE;
   else if (aLink.EqualsLiteral("import") &&
            nsStyleLinkElement::IsImportEnabled())
     return nsStyleLinkElement::eHTMLIMPORT;
+  else if (aLink.EqualsLiteral("preconnect"))
+    return nsStyleLinkElement::ePRECONNECT;
   else 
     return 0;
 }
 
 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes, nsIPrincipal* aPrincipal)
 {
   uint32_t linkMask = 0;
   nsAString::const_iterator start, done;
--- a/dom/base/nsStyleLinkElement.h
+++ b/dom/base/nsStyleLinkElement.h
@@ -54,17 +54,18 @@ public:
   virtual void SetLineNumber(uint32_t aLineNumber) MOZ_OVERRIDE;
 
   enum RelValue {
     ePREFETCH =     0x00000001,
     eDNS_PREFETCH = 0x00000002,
     eSTYLESHEET =   0x00000004,
     eNEXT =         0x00000008,
     eALTERNATE =    0x00000010,
-    eHTMLIMPORT =   0x00000020
+    eHTMLIMPORT =   0x00000020,
+    ePRECONNECT =   0x00000040
   };
 
   // The return value is a bitwise or of 0 or more RelValues.
   // aPrincipal is used to check if HTML imports is enabled for the
   // provided principal.
   static uint32_t ParseLinkTypes(const nsAString& aTypes,
                                  nsIPrincipal* aPrincipal);
 
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -16,16 +16,17 @@
 #include "nsContentUtils.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsDOMTokenList.h"
 #include "nsIDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsINode.h"
+#include "nsISpeculativeConnect.h"
 #include "nsIStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsReadableUtils.h"
 #include "nsStyleConsts.h"
 #include "nsUnicharUtils.h"
@@ -142,16 +143,20 @@ HTMLLinkElement::BindToTree(nsIDocument*
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
+  if (IsInComposedDoc()) {
+    UpdatePreconnect();
+  }
+
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, updateImport));
 
   CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
 
@@ -287,16 +292,40 @@ HTMLLinkElement::UpdateImport()
     // The load even might fire sooner than we could set mImportLoader so
     // we must use async event and a scriptBlocker here.
     nsAutoScriptBlocker scriptBlocker;
     // CORS check will happen at the start of the load.
     mImportLoader = manager->Get(uri, this, doc);
   }
 }
 
+void
+HTMLLinkElement::UpdatePreconnect()
+{
+  // rel type should be preconnect
+  nsAutoString rel;
+  if (!GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
+    return;
+  }
+
+  uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel, NodePrincipal());
+  if (!(linkTypes & ePRECONNECT)) {
+    return;
+  }
+
+  nsCOMPtr<nsISpeculativeConnect>
+    speculator(do_QueryInterface(nsContentUtils::GetIOService()));
+  if (speculator) {
+    nsCOMPtr<nsIURI> uri = GetHrefURI();
+    if (uri) {
+      speculator->SpeculativeConnect(uri, nullptr);
+    }
+  }
+}
+
 nsresult
 HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                          nsIAtom* aPrefix, const nsAString& aValue,
                          bool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
@@ -321,21 +350,26 @@ HTMLLinkElement::SetAttr(int32_t aNameSp
     bool dropSheet = false;
     if (aName == nsGkAtoms::rel) {
       uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(aValue,
                                                               NodePrincipal());
       if (GetSheet()) {
         dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
       } else if (linkTypes & eHTMLIMPORT) {
         UpdateImport();
+      } else if ((linkTypes & ePRECONNECT) && IsInComposedDoc()) {
+        UpdatePreconnect();
       }
     }
 
     if (aName == nsGkAtoms::href) {
       UpdateImport();
+      if (IsInComposedDoc()) {
+        UpdatePreconnect();
+      }
     }
     
     UpdateStyleSheetInternal(nullptr, nullptr,
                              dropSheet ||
                              (aName == nsGkAtoms::title ||
                               aName == nsGkAtoms::media ||
                               aName == nsGkAtoms::type));
   }
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -38,16 +38,17 @@ public:
 
   // DOM memory reporter participant
   NS_DECL_SIZEOF_EXCLUDING_THIS
 
   void LinkAdded();
   void LinkRemoved();
 
   void UpdateImport();
+  void UpdatePreconnect();
 
   // nsIDOMEventTarget
   virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) MOZ_OVERRIDE;
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) MOZ_OVERRIDE;
 
   // nsINode
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const MOZ_OVERRIDE;
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -35,16 +35,17 @@
 #include "nsEscape.h"
 #include "RemoteOpenFileParent.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsPrincipal.h"
 #include "nsIOService.h"
 #include "mozilla/net/OfflineObserver.h"
+#include "nsISpeculativeConnect.h"
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::TabContext;
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
 using mozilla::net::PTCPServerSocketParent;
 using mozilla::dom::TCPServerSocketParent;
@@ -664,16 +665,27 @@ NeckoParent::RecvPRemoteOpenFileConstruc
 bool
 NeckoParent::DeallocPRemoteOpenFileParent(PRemoteOpenFileParent* actor)
 {
   delete actor;
   return true;
 }
 
 bool
+NeckoParent::RecvSpeculativeConnect(const URIParams &aURI)
+{
+  nsCOMPtr<nsISpeculativeConnect> speculator(gIOService);
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+  if (uri && speculator) {
+    speculator->SpeculativeConnect(uri, nullptr);
+  }
+  return true;
+}
+
+bool
 NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
                                  const uint16_t& flags)
 {
   nsHTMLDNSPrefetch::Prefetch(hostname, flags);
   return true;
 }
 
 bool
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -155,16 +155,17 @@ protected:
   virtual PDNSRequestParent* AllocPDNSRequestParent(const nsCString& aHost,
                                                     const uint32_t& aFlags,
                                                     const nsCString& aNetworkInterface) MOZ_OVERRIDE;
   virtual bool RecvPDNSRequestConstructor(PDNSRequestParent* actor,
                                           const nsCString& hostName,
                                           const uint32_t& flags,
                                           const nsCString& aNetworkInterface) MOZ_OVERRIDE;
   virtual bool DeallocPDNSRequestParent(PDNSRequestParent*) MOZ_OVERRIDE;
+  virtual bool RecvSpeculativeConnect(const URIParams& aURI) MOZ_OVERRIDE;
   virtual bool RecvHTMLDNSPrefetch(const nsString& hostname,
                                    const uint16_t& flags) MOZ_OVERRIDE;
   virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname,
                                          const uint16_t& flags,
                                          const nsresult& reason) MOZ_OVERRIDE;
 
   virtual mozilla::ipc::IProtocol*
   CloneProtocol(Channel* aChannel,
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -68,16 +68,17 @@ parent:
   PUDPSocket(nsCString filter);
 
   PDNSRequest(nsCString hostName, uint32_t flags, nsCString networkInterface);
 
   PRemoteOpenFile(SerializedLoadContext loadContext,
                   URIParams fileuri,
                   OptionalURIParams appuri);
 
+  SpeculativeConnect(URIParams uri);
   HTMLDNSPrefetch(nsString hostname, uint16_t flags);
   CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
   PRtspController();
   PRtspChannel(RtspChannelConnectArgs args);
   PChannelDiverter(ChannelDiverterArgs channel);
 
   /**
    * These are called from the child with the results of the auth prompt.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -45,16 +45,17 @@
 #include "nsITimer.h"
 #include "nsCRT.h"
 #include "SpdyZlibReporter.h"
 #include "nsIMemoryReporter.h"
 #include "nsIParentalControlsService.h"
 #include "nsINetworkLinkService.h"
 
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/ipc/URIUtils.h"
 #include "mozilla/Telemetry.h"
 
 #if defined(XP_UNIX)
 #include <sys/utsname.h>
 #endif
 
 #if defined(XP_WIN)
 #include <windows.h>
@@ -1984,16 +1985,23 @@ nsHttpHandler::Observe(nsISupports *subj
 }
 
 // nsISpeculativeConnect
 
 NS_IMETHODIMP
 nsHttpHandler::SpeculativeConnect(nsIURI *aURI,
                                   nsIInterfaceRequestor *aCallbacks)
 {
+    if (IsNeckoChild()) {
+        ipc::URIParams params;
+        SerializeURI(aURI, params);
+        gNeckoChild->SendSpeculativeConnect(params);
+        return NS_OK;
+    }
+
     if (!mHandlerActive)
         return NS_OK;
 
     nsISiteSecurityService* sss = gHttpHandler->GetSSService();
     bool isStsHost = false;
     if (!sss)
         return NS_OK;
 
--- a/netwerk/test/mochitests/mochitest.ini
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -1,16 +1,18 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || e10s || toolkit == 'android' # Android: Bug 1111137 & 1078267
 
 support-files =
   method.sjs
   partial_content.sjs
+  rel_preconnect.sjs
   user_agent.sjs
   user_agent_update.sjs
 
 [test_arraybufferinputstream.html]
 [test_partially_cached_content.html]
+[test_rel_preconnect.html]
 [test_uri_scheme.html]
 [test_user_agent_overrides.html]
 [test_user_agent_updates.html]
 [test_user_agent_updates_reset.html]
 [test_xhr_method_case.html]
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/rel_preconnect.sjs
@@ -0,0 +1,12 @@
+// Generate response header "Link: <HREF>; rel=preconnect"
+// HREF is provided by the request header X-Link
+
+function handleRequest(request, response)
+{
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Link", "<" + 
+                       request.getHeader('X-Link') +
+                       ">; rel=preconnect");
+    response.write("check that header");
+}
+
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+  <title>Test for link rel=preconnect</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css"  href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
+var srv;
+
+function TestServer(nextTest) {
+    this.listener= Cc["@mozilla.org/network/server-socket;1"]
+                   .createInstance(Ci.nsIServerSocket);
+    this.listener.init(-1, true, -1);
+    this.listener.asyncListen(SpecialPowers.wrapCallbackObject(this));
+    this.nextTest = nextTest;
+}
+
+TestServer.prototype = {
+    QueryInterface: function(iid) {
+      iid = SpecialPowers.wrap(iid);
+        if (iid.equals(Ci.nsIServerSocketListener) ||
+            iid.equals(Ci.nsISupports))
+            return this;
+        throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+    onSocketAccepted: function(socket, trans) {
+        try { socket.close(); } catch(e) {}
+        try { trans.close(); } catch(e) {}
+        ok(true, "received connect");
+        setTimeout(srv.nextTest, 0);
+    },
+    onStopListening: function(socket) {}
+};
+
+var originalLimit = SpecialPowers.getIntPref("network.http.speculative-parallel-limit");
+
+function testElement()
+{
+  // test the link rel=preconnect element in the head
+  srv = new TestServer(testHeader);
+  SpecialPowers.setIntPref("network.http.speculative-parallel-limit", 1);
+  var link = document.createElement("link");
+  link.rel = "preconnect";
+  link.href = "//localhost:" +  srv.listener.port;
+  document.head.appendChild(link);
+}
+
+function testHeader()
+{
+  // test the http link response header
+  srv.listener.close();
+  srv = new TestServer(testDone);
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", 'rel_preconnect.sjs', false);
+  xhr.setRequestHeader("X-Link", "//localhost:" + srv.listener.port);
+  xhr.send();
+  is(xhr.status, 200, 'xhr cool');
+}
+
+function testDone()
+{
+  SpecialPowers.setIntPref("network.http.speculative-parallel-limit",
+                            originalLimit);
+  srv.listener.close();
+  SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="testElement();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+