Bug 580313 - New resource hints for link. r=smaug
authorDragana Damjanovic <dd.mozilla@gmail.com>
Fri, 26 Feb 2016 02:41:00 +0100
changeset 308779 c4d101e34585cb7950189fc1f4aa0bf99dec8041
parent 308778 ef1fb285a58d19d1aa8874d0fd9ce3e4bb554e40
child 308780 5b20181a5b50b92259038a88202b2ece37e2b702
push id9214
push userraliiev@mozilla.com
push dateMon, 07 Mar 2016 14:25:21 +0000
treeherdermozilla-aurora@8849dd1a4a79 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs580313
milestone47.0a1
Bug 580313 - New resource hints for link. r=smaug
dom/base/Link.cpp
dom/base/Link.h
dom/base/nsContentSink.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/test/unit/test_cancelPrefetch.js
dom/base/test/unit/xpcshell.ini
dom/html/HTMLLinkElement.cpp
dom/html/HTMLLinkElement.h
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_link_prefetch.html
dom/xml/nsXMLContentSink.cpp
netwerk/test/mochitests/rel_preconnect.sjs
netwerk/test/mochitests/test_rel_preconnect.html
parser/html/nsHtml5DocumentBuilder.cpp
uriloader/prefetch/nsIPrefetchService.idl
uriloader/prefetch/nsPrefetchService.cpp
uriloader/prefetch/nsPrefetchService.h
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -6,16 +6,19 @@
 
 #include "Link.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Element.h"
 #include "nsIURL.h"
 #include "nsISizeOf.h"
+#include "nsIDocShell.h"
+#include "nsIPrefetchService.h"
+#include "nsCPrefetchService.h"
 
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsString.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Services.h"
@@ -68,16 +71,80 @@ Link::CancelDNSPrefetch(nsWrapperCache::
     mElement->UnsetFlags(aRequestedFlag);
     // Possible that hostname could have changed since binding, but since this
     // covers common cases, most DNS prefetch requests will be canceled
     nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
   }
 }
 
 void
+Link::TryDNSPrefetchPreconnectOrPrefetch()
+{
+  MOZ_ASSERT(mElement->IsInComposedDoc());
+  if (!ElementHasHref()) {
+    return;
+  }
+
+  nsAutoString rel;
+  if (!mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
+    return;
+  }
+
+  if (!nsContentUtils::PrefetchEnabled(mElement->OwnerDoc()->GetDocShell())) {
+    return;
+  }
+
+  uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel,
+                         mElement->NodePrincipal());
+
+  if ((linkTypes & nsStyleLinkElement::ePREFETCH) ||
+      (linkTypes & nsStyleLinkElement::eNEXT)){
+    nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+    if (prefetchService) {
+      nsCOMPtr<nsIURI> uri(GetURI());
+      if (uri) {
+        nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
+        prefetchService->PrefetchURI(uri,
+                                     mElement->OwnerDoc()->GetDocumentURI(),
+                                     domNode, true);
+        return;
+      }
+    }
+  }
+
+  if (linkTypes & nsStyleLinkElement::ePRECONNECT) {
+    nsCOMPtr<nsIURI> uri(GetURI());
+    if (uri && mElement->OwnerDoc()) {
+      mElement->OwnerDoc()->MaybePreconnect(uri,
+        mElement->AttrValueToCORSMode(mElement->GetParsedAttr(nsGkAtoms::crossorigin)));
+      return;
+    }
+  }
+
+  if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
+    if (nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
+      nsHTMLDNSPrefetch::PrefetchLow(this);
+    }
+  }
+}
+
+void
+Link::CancelPrefetch()
+{
+  nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+  if (prefetchService) {
+    nsCOMPtr<nsIURI> uri(GetURI());
+    if (uri) {
+      nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
+      prefetchService->CancelPrefetchURI(uri, domNode);
+    }
+  }
+}
+
+void
 Link::SetLinkState(nsLinkState aState)
 {
   NS_ASSERTION(mRegistered,
                "Setting the link state of an unregistered Link!");
   NS_ASSERTION(mLinkState != aState,
                "Setting state to the currently set state!");
 
   // Set our current state as appropriate.
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -106,21 +106,25 @@ public:
    */
   virtual bool HasDeferredDNSPrefetchRequest() { return true; }
 
   virtual size_t
     SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   bool ElementHasHref() const;
 
+  // This is called by HTMLAnchorElement.
   void TryDNSPrefetch();
-
   void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
                          nsWrapperCache::FlagsType aRequestedFlag);
 
+  // This is called by HTMLLinkElement.
+  void TryDNSPrefetchPreconnectOrPrefetch();
+  void CancelPrefetch();
+
 protected:
   virtual ~Link();
 
   /**
    * Return true if the link has associated URI.
    */
   bool HasURI() const
   {
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -687,17 +687,21 @@ nsContentSink::ProcessLink(const nsSubst
 
   // The link relation may apply to a different resource, specified
   // in the anchor parameter. For the link relations supported so far,
   // we simply abort if the link applies to a resource different to the
   // one we've loaded
   if (!LinkContextIsOurDocument(aAnchor)) {
     return NS_OK;
   }
-  
+
+  if (!nsContentUtils::PrefetchEnabled(mDocShell)) {
+    return NS_OK;
+  }
+
   bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
   // prefetch href if relation is "next" or "prefetch"
   if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
     PrefetchHref(aHref, mDocument, hasPrefetch);
   }
 
   if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) {
     PrefetchDNS(aHref);
@@ -822,45 +826,16 @@ nsContentSink::ProcessMETATag(nsIContent
 }
 
 
 void
 nsContentSink::PrefetchHref(const nsAString &aHref,
                             nsINode *aSource,
                             bool aExplicit)
 {
-  //
-  // SECURITY CHECK: disable prefetching from mailnews!
-  //
-  // walk up the docshell tree to see if any containing
-  // docshell are of type MAIL.
-  //
-  if (!mDocShell)
-    return;
-
-  nsCOMPtr<nsIDocShell> docshell = mDocShell;
-
-  nsCOMPtr<nsIDocShellTreeItem> parentItem;
-  do {
-    uint32_t appType = 0;
-    nsresult rv = docshell->GetAppType(&appType);
-    if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL)
-      return; // do not prefetch from mailnews
-    docshell->GetParent(getter_AddRefs(parentItem));
-    if (parentItem) {
-      docshell = do_QueryInterface(parentItem);
-      if (!docshell) {
-        NS_ERROR("cannot get a docshell from a treeItem!");
-        return;
-      }
-    }
-  } while (parentItem);
-  
-  // OK, we passed the security check...
-  
   nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
   if (prefetchService) {
     // 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());
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7228,16 +7228,53 @@ nsContentUtils::GenerateUUIDInPlace(nsID
   nsresult rv = sUUIDGenerator->GenerateUUIDInPlace(&aUUID);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+bool
+nsContentUtils::PrefetchEnabled(nsIDocShell* aDocShell)
+{
+  //
+  // SECURITY CHECK: disable prefetching from mailnews!
+  //
+  // walk up the docshell tree to see if any containing
+  // docshell are of type MAIL.
+  //
+
+  if (!aDocShell) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDocShell> docshell = aDocShell;
+  nsCOMPtr<nsIDocShellTreeItem> parentItem;
+
+  do {
+    uint32_t appType = 0;
+    nsresult rv = docshell->GetAppType(&appType);
+    if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL) {
+      return false; // do not prefetch, preconnect from mailnews
+    }
+
+    docshell->GetParent(getter_AddRefs(parentItem));
+    if (parentItem) {
+      docshell = do_QueryInterface(parentItem);
+      if (!docshell) {
+        NS_ERROR("cannot get a docshell from a treeItem!");
+        return false;
+      }
+    }
+  } while (parentItem);
+
+  return true;
+}
+
 uint64_t
 nsContentUtils::GetInnerWindowID(nsIRequest* aRequest)
 {
   // can't do anything if there's no nsIRequest!
   if (!aRequest) {
     return 0;
   }
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -912,16 +912,17 @@ public:
    */
   static uint32_t ParseSandboxAttributeToFlags(const nsAttrValue* sandboxAttr);
 
   /**
    * Helper function that generates a UUID.
    */
   static nsresult GenerateUUIDInPlace(nsID& aUUID);
 
+  static bool PrefetchEnabled(nsIDocShell* aDocShell);
 
   /**
    * Fill (with the parameters given) the localized string named |aKey| in
    * properties file |aFile|.
    */
 private:
   static nsresult FormatLocalizedString(PropertiesFile aFile,
                                         const char* aKey,
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/test_cancelPrefetch.js
@@ -0,0 +1,134 @@
+//Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+var prefetch = Cc["@mozilla.org/prefetch-service;1"].
+               getService(Ci.nsIPrefetchService);
+var ios = Cc["@mozilla.org/network/io-service;1"].
+          getService(Ci.nsIIOService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+            getService(Ci.nsIPrefBranch);
+
+var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+             createInstance(Ci.nsIDOMParser);
+
+var doc;
+
+var docbody = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' +
+              '<link id="node1"/><link id="node2"/>' +
+              '</body></html>';
+
+var node1;
+var node2;
+
+function run_test() {
+  prefs.setBoolPref("network.prefetch-next", true);
+
+  parser.init();
+  doc = parser.parseFromString(docbody, "text/html");
+
+  node1 = doc.getElementById("node1");
+  node2 = doc.getElementById("node2");
+
+  run_next_test();
+}
+
+add_test(function test_cancel1() {
+
+  var uri = ios.newURI("http://localhost/1", null, null);
+  prefetch.prefetchURI(uri, uri, node1, true);
+
+  do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
+
+  // Trying to prefetch again the same uri with the same node will fail.
+  var didFail = 0;
+
+  try {
+    prefetch.prefetchURI(uri, uri, node1, true);
+  } catch(e) {
+    didFail = 1;
+  }
+
+  do_check_true(didFail == 1, 'Prefetching the same request with the same ' +
+                              'node fails.');
+
+  do_check_true(prefetch.hasMoreElements(), 'There is still request in ' +
+                                            'the queue');
+
+  prefetch.cancelPrefetchURI(uri, node1);
+
+  do_check_false(prefetch.hasMoreElements(), 'There is no request in the ' +
+                                             'queue');
+  run_next_test();
+});
+
+add_test(function test_cancel2() {
+  // Prefetch a uri with 2 different nodes. There should be 2 request
+  // in the queue and canceling one will not cancel the other.
+
+  var uri = ios.newURI("http://localhost/1", null, null);
+  prefetch.prefetchURI(uri, uri, node1, true);
+  prefetch.prefetchURI(uri, uri, node2, true);
+
+  do_check_true(prefetch.hasMoreElements(), 'There are requests in the queue');
+
+  prefetch.cancelPrefetchURI(uri, node1);
+
+  do_check_true(prefetch.hasMoreElements(), 'There is still one more request ' +
+                                            'in the queue');
+
+  prefetch.cancelPrefetchURI(uri, node2);
+
+  do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
+  run_next_test();
+});
+
+add_test(function test_cancel3() {
+  // Request a prefetch of a uri. Trying to cancel a prefetch for the same uri
+  // with a different node will fail.
+  var uri = ios.newURI("http://localhost/1", null, null);
+  prefetch.prefetchURI(uri, uri, node1, true);
+
+  do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
+
+  var didFail = 0;
+
+  try {
+    prefetch.cancelPrefetchURI(uri, node2);
+  } catch(e) {
+    didFail = 1;
+  }
+  do_check_true(didFail == 1, 'Canceling the request failed');
+
+  do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
+                                            'in the queue');
+
+  prefetch.cancelPrefetchURI(uri, node1);
+  do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
+  run_next_test();
+});
+
+add_test(function test_cancel4() {
+  // Request a prefetch of a uri. Trying to cancel a prefetch for a different uri
+  // with the same node will fail.
+  var uri1 = ios.newURI("http://localhost/1", null, null);
+  var uri2 = ios.newURI("http://localhost/2", null, null);
+  prefetch.prefetchURI(uri1, uri1, node1, true);
+
+  do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
+
+  var didFail = 0;
+
+  try {
+    prefetch.cancelPrefetchURI(uri2, node1);
+  } catch(e) {
+    didFail = 1;
+  }
+  do_check_true(didFail == 1, 'Canceling the request failed');
+
+  do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
+                                            'in the queue');
+
+  prefetch.cancelPrefetchURI(uri1, node1);
+  do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
+  run_next_test();
+});
--- a/dom/base/test/unit/xpcshell.ini
+++ b/dom/base/test/unit/xpcshell.ini
@@ -45,8 +45,9 @@ head = head_xml.js
 head = head_xml.js
 [test_xhr_document.js]
 [test_xhr_standalone.js]
 [test_xml_parser.js]
 head = head_xml.js
 [test_xml_serializer.js]
 head = head_xml.js
 [test_xmlserializer.js]
+[test_cancelPrefetch.js]
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -175,20 +175,17 @@ HTMLLinkElement::BindToTree(nsIDocument*
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   if (IsInComposedDoc()) {
-    UpdatePreconnect();
-    if (HasDNSPrefetchRel()) {
-      TryDNSPrefetch();
-    }
+    TryDNSPrefetchPreconnectOrPrefetch();
   }
 
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, updateImport));
 
@@ -212,16 +209,17 @@ HTMLLinkElement::LinkRemoved()
 void
 HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Cancel any DNS prefetches
   // Note: Must come before ResetLinkState.  If called after, it will recreate
   // mCachedURI based on data that is invalid - due to a call to GetHostname.
   CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
                     HTML_LINK_DNS_PREFETCH_REQUESTED);
+  CancelPrefetch();
 
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   // If this is reinserted back into the document it will not be
   // from the parser.
   nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
@@ -339,59 +337,25 @@ 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;
-  }
-
-  nsIDocument *owner = OwnerDoc();
-  if (owner) {
-    nsCOMPtr<nsIURI> uri = GetHrefURI();
-    if (uri) {
-        owner->MaybePreconnect(uri, GetCORSMode());
-    }
-  }
-}
-
-bool
-HTMLLinkElement::HasDNSPrefetchRel()
-{
-  nsAutoString rel;
-  if (GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
-    return !!(ParseLinkTypes(rel, NodePrincipal()) &
-              nsStyleLinkElement::eDNS_PREFETCH);
-  }
-
-  return false;
-}
-
 nsresult
 HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                nsAttrValueOrString* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
     CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
                       HTML_LINK_DNS_PREFETCH_REQUESTED);
+    CancelPrefetch();
   }
 
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
                                              aValue, aNotify);
 }
 
 nsresult
 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
@@ -422,31 +386,26 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
         nsAutoString value;
         aValue->ToString(value);
         uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value,
                                                                 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();
-        }
       }
 
       if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
-          HasDNSPrefetchRel() && IsInComposedDoc()) {
-        TryDNSPrefetch();
+          IsInComposedDoc()) {
+        TryDNSPrefetchPreconnectOrPrefetch();
       }
 
       UpdateStyleSheetInternal(nullptr, nullptr,
                                dropSheet ||
                                (aName == nsGkAtoms::title ||
                                 aName == nsGkAtoms::media ||
                                 aName == nsGkAtoms::type));
     }
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -39,17 +39,16 @@ public:
 
   // DOM memory reporter participant
   NS_DECL_SIZEOF_EXCLUDING_THIS
 
   void LinkAdded();
   void LinkRemoved();
 
   void UpdateImport();
-  void UpdatePreconnect();
 
   // nsIDOMEventTarget
   virtual nsresult PreHandleEvent(EventChainPreVisitor& aVisitor) override;
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) override;
 
   // nsINode
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const override;
@@ -169,18 +168,16 @@ protected:
                                  nsAString& aMedia,
                                  bool* aIsScoped,
                                  bool* aIsAlternate) override;
 protected:
   // nsGenericHTMLElement
   virtual void GetItemValueText(DOMString& text) override;
   virtual void SetItemValueText(const nsAString& text) override;
 
-  bool HasDNSPrefetchRel();
-
   RefPtr<nsDOMTokenList > mRelList;
 private:
   RefPtr<ImportLoader> mImportLoader;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -36,8 +36,9 @@ support-files =
 [test_shadowroot_inert_element.html]
 [test_shadowroot_host.html]
 [test_shadowroot_style.html]
 [test_shadowroot_style_multiple_shadow.html]
 [test_shadowroot_style_order.html]
 [test_shadowroot_youngershadowroot.html]
 [test_style_fallback_content.html]
 [test_unresolved_pseudo_class.html]
+[test_link_prefetch.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_link_prefetch.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=580313
+-->
+<head>
+  <title>Test Prefetch (bug 580313)</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580313">Mozilla Bug 580313</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+  SimpleTest.waitForExplicitFinish();
+
+  var prefetch = SpecialPowers.Cc["@mozilla.org/prefetch-service;1"].
+                   getService(SpecialPowers.Ci.nsIPrefetchService);
+  var ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"].
+            getService(SpecialPowers.Ci.nsIIOService);
+
+  is(prefetch.hasMoreElements(), false, "No prefetches at the test start.");
+
+  var linkElem = document.createElement('link');
+  linkElem.rel = "prefetch";
+
+  // Href is empty.
+  document.head.appendChild(linkElem);
+  is(prefetch.hasMoreElements(), false,
+     "If href is not a valid uri, a prefetch has not been started.");
+
+  // Change uri of an existing link. Now it is a valid uri and
+  // a prefetch should start.
+  linkElem.href = "https://example.com/1";
+  is(prefetch.hasMoreElements(), true,
+     "Setting the href to a valid uri has started a new prefetch.");
+
+  // Removing a link, removes its prefetch.
+  document.head.removeChild(linkElem);
+  is(prefetch.hasMoreElements(), false,
+     "Removing the link has canceled the prefetch.");
+
+  // Add link again.
+  document.head.appendChild(linkElem);
+  is(prefetch.hasMoreElements(), true,
+     "Adding link again, has started the prefetch again.");
+
+  // Changing the href should cancel the current prefetch.
+  linkElem.href = "https://example.com/2";
+  is(prefetch.hasMoreElements(), true,
+     "Changing href, a new prefetch has been started.");
+  // To check if "https://example.com/1" prefetch has been canceled, we try to
+  // cancel it using PrefetService. Since the prefetch for
+  // "https://example.com/1" does not exist, the cancel will throw.
+  var cancelError = 0;
+  try {
+    var uri = ios.newURI("https://example.com/1", null, null);
+    prefetch.cancelPrefetchURI(uri, linkElem);
+  } catch(e) {
+    cancelError = 1;
+  }
+  is(cancelError, 1, "This prefetch has aleady been canceled");
+
+  // Now cancel the right uri.
+  cancelError = 0;
+  try {
+    var uri = ios.newURI("https://example.com/2", null, null);
+    prefetch.cancelPrefetchURI(uri, linkElem);
+  } catch(e) {
+    cancelError = 1;
+  }
+  is(cancelError, 0, "This prefetch has been canceled successfully");
+
+  is(prefetch.hasMoreElements(), false, "The prefetch has already been canceled.");
+
+  // Removing the link will do nothing regarding prefetch service.
+  document.head.removeChild(linkElem);
+
+  // Adding two links to the same uri and removing one will not remove the other.
+  document.head.appendChild(linkElem);
+  is(prefetch.hasMoreElements(), true,
+     "Added one prefetch for 'https://example.com/2'.");
+
+  var linkElem2 = document.createElement('link');
+  linkElem2.rel = "prefetch";
+  linkElem2.href = "https://example.com/2";
+  document.head.appendChild(linkElem2);
+  is(prefetch.hasMoreElements(), true,
+     "Added second prefetch for 'https://example.com/2'.");
+
+  // Remove first link element. This should not remove the prefetch.
+  document.head.removeChild(linkElem);
+  is(prefetch.hasMoreElements(), true,
+     "The prefetch for 'https://example.com/2' is still present.");
+
+  // Remove the second link element. This should remove the prefetch.
+  document.head.removeChild(linkElem2);
+  is(prefetch.hasMoreElements(), false,
+     "There is no prefetch.");
+
+  SimpleTest.finish();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -590,34 +590,16 @@ nsXMLContentSink::CloseElement(nsIConten
       rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nullptr : this,
                                   &willNotify,
                                   &isAlternate);
       if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) {
         ++mPendingSheetCount;
         mScriptLoader->AddExecuteBlocker();
       }
     }
-    // Look for <link rel="dns-prefetch" href="hostname">
-    // and look for <link rel="next" href="hostname"> like in HTML sink
-    if (nodeInfo->Equals(nsGkAtoms::link, kNameSpaceID_XHTML)) {
-      nsAutoString relVal;
-      aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relVal);
-      if (!relVal.IsEmpty()) {
-        uint32_t linkTypes =
-          nsStyleLinkElement::ParseLinkTypes(relVal, aContent->NodePrincipal());
-        bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
-        if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
-          nsAutoString hrefVal;
-          aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
-          if (!hrefVal.IsEmpty()) {
-            PrefetchHref(hrefVal, aContent, hasPrefetch);
-          }
-        }
-      }
-    }
   }
 
   return rv;
 }
 
 nsresult
 nsXMLContentSink::AddContentAsLeaf(nsIContent *aContent)
 {
--- a/netwerk/test/mochitests/rel_preconnect.sjs
+++ b/netwerk/test/mochitests/rel_preconnect.sjs
@@ -1,15 +1,15 @@
 // 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') +
+                       request.queryString +
                        ">; rel=preconnect" + ", " +
                         "<" + 
-                       request.getHeader('X-Link') +
+                       request.queryString +
                        ">; rel=preconnect; crossOrigin=anonymous");
     response.write("check that header");
 }
 
--- a/netwerk/test/mochitests/test_rel_preconnect.html
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -65,21 +65,20 @@ function doTest()
   link = document.createElement("link");
   link.rel = "preconnect";
   link.href = "//localhost:" +  srv.listener.port;
   link.crossOrigin = "anonymous";
   document.head.appendChild(link);
 
   // test the http link response header - the test contains both a
   // normal and anonymous preconnect link header
-  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');
+  var iframe = document.createElement('iframe');
+  iframe.src = 'rel_preconnect.sjs?//localhost:' + srv.listener.port;
+
+  document.body.appendChild(iframe);
 }
 
 </script>
 </head>
 <body onload="doTest();">
 <p id="display"></p>
 <div id="content" style="display: none"></div>
 <pre id="test">
--- a/parser/html/nsHtml5DocumentBuilder.cpp
+++ b/parser/html/nsHtml5DocumentBuilder.cpp
@@ -76,34 +76,16 @@ nsHtml5DocumentBuilder::UpdateStyleSheet
   nsresult rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nullptr : this,
                                        &willNotify,
                                        &isAlternate);
   if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) {
     ++mPendingSheetCount;
     mScriptLoader->AddExecuteBlocker();
   }
 
-  if (aElement->IsHTMLElement(nsGkAtoms::link)) {
-    // look for <link rel="next" href="url">
-    nsAutoString relVal;
-    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relVal);
-    if (!relVal.IsEmpty()) {
-      uint32_t linkTypes =
-        nsStyleLinkElement::ParseLinkTypes(relVal, aElement->NodePrincipal());
-      bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
-      if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
-        nsAutoString hrefVal;
-        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
-        if (!hrefVal.IsEmpty()) {
-          PrefetchHref(hrefVal, aElement, hasPrefetch);
-        }
-      }
-    }
-  }
-
   // Re-open update
   BeginDocUpdate();
 }
 
 void
 nsHtml5DocumentBuilder::SetDocumentMode(nsHtml5DocumentMode m)
 {
   nsCompatibility mode = eCompatibility_NavQuirks;
--- a/uriloader/prefetch/nsIPrefetchService.idl
+++ b/uriloader/prefetch/nsIPrefetchService.idl
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIDOMNode;
 interface nsISimpleEnumerator;
 
-[scriptable, uuid(2df8b475-f536-4a1a-afea-b39843df8005)]
+[scriptable, uuid(422a1807-4e7f-463d-b8d7-ca2ceb9b5d53)]
 interface nsIPrefetchService : nsISupports
 {
     /**
      * Enqueue a request to prefetch the specified URI.
      *
      * @param aURI the URI of the document to prefetch
      * @param aReferrerURI the URI of the referring page
      * @param aSource the DOM node (such as a <link> tag) that requested this
@@ -25,10 +25,13 @@ interface nsIPrefetchService : nsISuppor
                      in nsIDOMNode aSource,
                      in boolean aExplicit);
 
     /**
      * Find out if there are any prefetches running or queued
      */
     boolean hasMoreElements();
 
-    // XXX do we need a way to cancel prefetch requests?
+    /**
+     * Cancel prefetch
+     */
+    void cancelPrefetchURI(in nsIURI aURI, in nsIDOMNode aSource);
 };
--- a/uriloader/prefetch/nsPrefetchService.cpp
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -77,26 +77,39 @@ nsPrefetchNode::nsPrefetchNode(nsPrefetc
                                nsIURI *aReferrerURI,
                                nsIDOMNode *aSource)
     : mURI(aURI)
     , mReferrerURI(aReferrerURI)
     , mService(aService)
     , mChannel(nullptr)
     , mBytesRead(0)
 {
-    mSource = do_GetWeakReference(aSource);
+    nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+    mSources.AppendElement(source);
 }
 
 nsresult
 nsPrefetchNode::OpenChannel()
 {
-    nsCOMPtr<nsINode> source = do_QueryReferent(mSource);
+    if (mSources.IsEmpty()) {
+        // Don't attempt to prefetch if we don't have a source node
+        // (which should never happen).
+        return NS_ERROR_FAILURE;
+    }
+    nsCOMPtr<nsINode> source;
+    while (!mSources.IsEmpty() && !(source = do_QueryReferent(mSources.ElementAt(0)))) {
+        // If source is null remove it.
+        // (which should never happen).
+        mSources.RemoveElementAt(0);
+    }
+
     if (!source) {
         // Don't attempt to prefetch if we don't have a source node
         // (which should never happen).
+
         return NS_ERROR_FAILURE;
     }
     nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
     CORSMode corsMode = CORS_NONE;
     if (source->IsHTMLElement(nsGkAtoms::link)) {
       corsMode = static_cast<dom::HTMLLinkElement*>(source.get())->GetCORSMode();
     }
     uint32_t securityFlags;
@@ -590,35 +603,56 @@ nsPrefetchService::Prefetch(nsIURI *aURI
         rv = url->GetQuery(query);
         if (NS_FAILED(rv) || !query.IsEmpty()) {
             LOG(("rejected: URL has a query string\n"));
             return NS_ERROR_ABORT;
         }
     }
 
     //
-    // cancel if being prefetched
+    // Check whether it is being prefetched.
     //
     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
         bool equals;
-        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) && equals) {
-            LOG(("rejected: URL is already being prefetched\n"));
-            return NS_ERROR_ABORT;
+        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
+            equals) {
+            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+            if (mCurrentNodes[i]->mSources.IndexOf(source) ==
+                mCurrentNodes[i]->mSources.NoIndex) {
+                LOG(("URL is already being prefetched, add a new reference "
+                     "document\n"));
+                mCurrentNodes[i]->mSources.AppendElement(source);
+                return NS_OK;
+            } else {
+                LOG(("URL is already being prefetched by this document"));
+                return NS_ERROR_ABORT;
+            }
         }
     }
 
     //
-    // cancel if already on the prefetch queue
+    // Check whether it is on the prefetch queue.
     //
-    for (std::deque<RefPtr<nsPrefetchNode>>::iterator node = mQueue.begin();
-         node != mQueue.end(); node++) {
+    for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
+         nodeIt != mQueue.end(); nodeIt++) {
         bool equals;
-        if (NS_SUCCEEDED(node->get()->mURI->Equals(aURI, &equals)) && equals) {
-            LOG(("rejected: URL is already on prefetch queue\n"));
-            return NS_ERROR_ABORT;
+        RefPtr<nsPrefetchNode> node = nodeIt->get();
+        if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+            if (node->mSources.IndexOf(source) ==
+                node->mSources.NoIndex) {
+                LOG(("URL is already being prefetched, add a new reference "
+                     "document\n"));
+                node->mSources.AppendElement(do_GetWeakReference(aSource));
+                return NS_OK;
+            } else {
+                LOG(("URL is already being prefetched by this document"));
+                return NS_ERROR_ABORT;
+            }
+     
         }
     }
 
     RefPtr<nsPrefetchNode> enqueuedNode;
     rv = EnqueueURI(aURI, aReferrerURI, aSource,
                     getter_AddRefs(enqueuedNode));
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -628,16 +662,82 @@ nsPrefetchService::Prefetch(nsIURI *aURI
     if (mStopCount == 0 && mHaveProcessed) {
         ProcessNextURI(nullptr);
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsPrefetchService::CancelPrefetchURI(nsIURI* aURI,
+                                     nsIDOMNode* aSource)
+{
+    NS_ENSURE_ARG_POINTER(aURI);
+
+    if (LOG_ENABLED()) {
+        nsAutoCString spec;
+        aURI->GetSpec(spec);
+        LOG(("CancelPrefetchURI [%s]\n", spec.get()));
+    }
+
+    //
+    // look in current prefetches
+    //
+    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+        bool equals;
+        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
+            equals) {
+            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+            if (mCurrentNodes[i]->mSources.IndexOf(source) !=
+                mCurrentNodes[i]->mSources.NoIndex) {
+                mCurrentNodes[i]->mSources.RemoveElement(source);
+                if (mCurrentNodes[i]->mSources.IsEmpty()) {
+                    mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+                    mCurrentNodes.RemoveElementAt(i);
+                }
+                return NS_OK;
+            }
+            return NS_ERROR_FAILURE;
+        }
+    }
+
+    //
+    // look into the prefetch queue
+    //
+    for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mQueue.begin();
+         nodeIt != mQueue.end(); nodeIt++) {
+        bool equals;
+        RefPtr<nsPrefetchNode> node = nodeIt->get();
+        if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
+            nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
+            if (node->mSources.IndexOf(source) !=
+                node->mSources.NoIndex) {
+
+#ifdef DEBUG
+                int32_t inx = node->mSources.IndexOf(source);
+                nsCOMPtr<nsIDOMNode> domNode =
+                    do_QueryReferent(node->mSources.ElementAt(inx));
+                MOZ_ASSERT(domNode);
+#endif
+
+                node->mSources.RemoveElement(source);
+                if (node->mSources.IsEmpty()) {
+                    mQueue.erase(nodeIt);
+                }
+                return NS_OK;
+            }
+            return NS_ERROR_FAILURE;
+        }
+    }
+
+    // not found!
+    return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
 nsPrefetchService::PrefetchURI(nsIURI *aURI,
                                nsIURI *aReferrerURI,
                                nsIDOMNode *aSource,
                                bool aExplicit)
 {
     return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
 }
 
--- a/uriloader/prefetch/nsPrefetchService.h
+++ b/uriloader/prefetch/nsPrefetchService.h
@@ -92,19 +92,19 @@ public:
     nsPrefetchNode(nsPrefetchService *aPrefetchService,
                    nsIURI *aURI,
                    nsIURI *aReferrerURI,
                    nsIDOMNode *aSource);
 
     nsresult OpenChannel();
     nsresult CancelChannel(nsresult error);
 
-    nsCOMPtr<nsIURI>            mURI;
-    nsCOMPtr<nsIURI>            mReferrerURI;
-    nsCOMPtr<nsIWeakReference>  mSource;
+    nsCOMPtr<nsIURI>                      mURI;
+    nsCOMPtr<nsIURI>                      mReferrerURI;
+    nsTArray<nsCOMPtr<nsIWeakReference>>  mSources;
 
 private:
     ~nsPrefetchNode() {}
 
     RefPtr<nsPrefetchService>   mService;
     nsCOMPtr<nsIChannel>        mChannel;
     nsCOMPtr<nsIChannel>        mRedirectChannel;
     int64_t                     mBytesRead;