Bug 580313 - New resource hints for link. r=smaug
authorDragana Damjanovic <dd.mozilla@gmail.com>
Fri, 26 Feb 2016 02:41:00 +0100
changeset 322142 c4d101e34585cb7950189fc1f4aa0bf99dec8041
parent 322141 ef1fb285a58d19d1aa8874d0fd9ce3e4bb554e40
child 322143 5b20181a5b50b92259038a88202b2ece37e2b702
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs580313
milestone47.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 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;