Bug 1134648, handle dynamic changes to rel=dns-prefetch, r=bz
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 01 Dec 2015 19:22:20 +0200
changeset 309223 3254063d0c0a5fc412d0685dea8b9fef4b4bfa3c
parent 309222 4eb8f2d4c0262ed6870f9cd3f28b0de3d1c250b0
child 309224 f61e7d37cd1be5679c2ed507e7908dac93c5eb15
push idunknown
push userunknown
push dateunknown
reviewersbz
bugs1134648
milestone45.0a1
Bug 1134648, handle dynamic changes to rel=dns-prefetch, r=bz
dom/base/Link.cpp
dom/base/Link.h
dom/html/HTMLAnchorElement.cpp
dom/html/HTMLLinkElement.cpp
dom/html/HTMLLinkElement.h
dom/xml/nsXMLContentSink.cpp
parser/html/nsHtml5DocumentBuilder.cpp
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Element.h"
 #include "nsIURL.h"
 #include "nsISizeOf.h"
 
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
+#include "nsHTMLDNSPrefetch.h"
 #include "nsString.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Services.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -42,16 +43,41 @@ Link::ElementHasHref() const
 {
   return ((!mElement->IsSVGElement() &&
            mElement->HasAttr(kNameSpaceID_None, nsGkAtoms::href))
         || (!mElement->IsHTMLElement() &&
             mElement->HasAttr(kNameSpaceID_XLink, nsGkAtoms::href)));
 }
 
 void
+Link::TryDNSPrefetch()
+{
+  MOZ_ASSERT(mElement->IsInComposedDoc());
+  if (ElementHasHref() && nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
+    nsHTMLDNSPrefetch::PrefetchLow(this);
+  }
+}
+
+void
+Link::CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+                        nsWrapperCache::FlagsType aRequestedFlag)
+{
+  // If prefetch was deferred, clear flag and move on
+  if (mElement->HasFlag(aDeferredFlag)) {
+    mElement->UnsetFlags(aDeferredFlag);
+    // Else if prefetch was requested, clear flag and send cancellation
+  } else if (mElement->HasFlag(aRequestedFlag)) {
+    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::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,16 +106,21 @@ public:
    */
   virtual bool HasDeferredDNSPrefetchRequest() { return true; }
 
   virtual size_t
     SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   bool ElementHasHref() const;
 
+  void TryDNSPrefetch();
+
+  void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
+                         nsWrapperCache::FlagsType aRequestedFlag);
+
 protected:
   virtual ~Link();
 
   /**
    * Return true if the link has associated URI.
    */
   bool HasURI() const
   {
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -153,42 +153,31 @@ HTMLAnchorElement::BindToTree(nsIDocumen
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Prefetch links
   nsIDocument* doc = GetComposedDoc();
   if (doc) {
     doc->RegisterPendingLinkUpdate(this);
-    if (nsHTMLDNSPrefetch::IsAllowed(OwnerDoc())) {
-      nsHTMLDNSPrefetch::PrefetchLow(this);
-    }
+    TryDNSPrefetch();
   }
 
   return rv;
 }
 
 void
 HTMLAnchorElement::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_ANCHOR_DNS_PREFETCH_DEFERRED,
+                    HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
 
-  // If prefetch was deferred, clear flag and move on
-  if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_DEFERRED))
-    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_DEFERRED);
-  // Else if prefetch was requested, clear flag and send cancellation
-  else if (HasFlag(HTML_ANCHOR_DNS_PREFETCH_REQUESTED)) {
-    UnsetFlags(HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
-    // 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);
-  }
-  
   // 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());
 
   // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
   // return null.
   nsIDocument* doc = OwnerDoc();
   if (doc) {
@@ -401,46 +390,61 @@ HTMLAnchorElement::SetAttr(int32_t aName
     // However, if we have a cached URI, we'll want to see if the value changed.
     else {
       nsAutoString val;
       GetHref(val);
       if (!val.Equals(aValue)) {
         reset = true;
       }
     }
+    if (reset) {
+      CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+                        HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+    }
   }
 
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
   // The ordering of the parent class's SetAttr call and Link::ResetLinkState
   // is important here!  The attribute is not set until SetAttr returns, and
   // we will need the updated attribute value because notifying the document
   // that content states have changed will call IntrinsicState, which will try
   // to get updated information about the visitedness from Link.
   if (reset) {
     Link::ResetLinkState(!!aNotify, true);
+    if (IsInComposedDoc()) {
+      TryDNSPrefetch();
+    }
   }
 
   return rv;
 }
 
 nsresult
 HTMLAnchorElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify)
 {
+  bool href =
+    (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID);
+
+  if (href) {
+    CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
+                      HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+  }
+
   nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
                                                 aNotify);
 
   // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
   // is important here!  The attribute is not unset until UnsetAttr returns, and
   // we will need the updated attribute value because notifying the document
   // that content states have changed will call IntrinsicState, which will try
   // to get updated information about the visitedness from Link.
-  if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
+  if (href) {
     Link::ResetLinkState(!!aNotify, false);
   }
 
   return rv;
 }
 
 bool
 HTMLAnchorElement::ParseAttribute(int32_t aNamespaceID,
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -23,16 +23,32 @@
 #include "nsINode.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsIURL.h"
 #include "nsPIDOMWindow.h"
 #include "nsReadableUtils.h"
 #include "nsStyleConsts.h"
 #include "nsUnicharUtils.h"
 
+#define LINK_ELEMENT_FLAG_BIT(n_) \
+  NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// Link element specific bits
+enum {
+  // Indicates that a DNS Prefetch has been requested from this Link element.
+  HTML_LINK_DNS_PREFETCH_REQUESTED = LINK_ELEMENT_FLAG_BIT(0),
+
+  // Indicates that a DNS Prefetch was added to the deferral queue
+  HTML_LINK_DNS_PREFETCH_DEFERRED =  LINK_ELEMENT_FLAG_BIT(1)
+};
+
+#undef LINK_ELEMENT_FLAG_BIT
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 2);
+
 NS_IMPL_NS_NEW_HTML_ELEMENT(Link)
 
 namespace mozilla {
 namespace dom {
 
 HTMLLinkElement::HTMLLinkElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
   , Link(this)
@@ -121,16 +137,36 @@ HTMLLinkElement::GetItemValueText(DOMStr
 }
 
 void
 HTMLLinkElement::SetItemValueText(const nsAString& aValue)
 {
   SetHref(aValue);
 }
 
+void
+HTMLLinkElement::OnDNSPrefetchRequested()
+{
+  UnsetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+  SetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+}
+
+void
+HTMLLinkElement::OnDNSPrefetchDeferred()
+{
+  UnsetFlags(HTML_LINK_DNS_PREFETCH_REQUESTED);
+  SetFlags(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
+bool
+HTMLLinkElement::HasDeferredDNSPrefetchRequest()
+{
+  return HasFlag(HTML_LINK_DNS_PREFETCH_DEFERRED);
+}
+
 nsresult
 HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                             nsIContent* aBindingParent,
                             bool aCompileEventHandlers)
 {
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
@@ -140,16 +176,19 @@ HTMLLinkElement::BindToTree(nsIDocument*
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   if (IsInComposedDoc()) {
     UpdatePreconnect();
+    if (HasDNSPrefetchRel()) {
+      TryDNSPrefetch();
+    }
   }
 
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, updateImport));
 
@@ -168,16 +207,22 @@ void
 HTMLLinkElement::LinkRemoved()
 {
   CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkRemoved"));
 }
 
 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);
+
   // 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();
 
@@ -317,16 +362,42 @@ HTMLLinkElement::UpdatePreconnect()
   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);
+  }
+
+  return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
+                                             aValue, aNotify);
+}
+
 nsresult
 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                               const nsAttrValue* aValue, bool aNotify)
 {
   // It's safe to call ResetLinkState here because our new attr value has
   // already been set or unset.  ResetLinkState needs the updated attribute
   // value because notifying the document that content states have changed will
   // call IntrinsicState, which will try to get updated information about the
@@ -363,16 +434,21 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
 
       if (aName == nsGkAtoms::href) {
         UpdateImport();
         if (IsInComposedDoc()) {
           UpdatePreconnect();
         }
       }
 
+      if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
+          HasDNSPrefetchRel() && IsInComposedDoc()) {
+        TryDNSPrefetch();
+      }
+
       UpdateStyleSheetInternal(nullptr, nullptr,
                                dropSheet ||
                                (aName == nsGkAtoms::title ||
                                 aName == nsGkAtoms::media ||
                                 aName == nsGkAtoms::type));
     }
   } else {
     // Since removing href or rel makes us no longer link to a
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -56,32 +56,39 @@ public:
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
+  virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                                 nsAttrValueOrString* aValue,
+                                 bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
                                 bool aNotify) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   // Element
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
                               nsAttrValue& aResult) override;
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual EventStates IntrinsicState() const override;
 
   void CreateAndDispatchEvent(nsIDocument* aDoc, const nsAString& aEventName);
 
+  virtual void OnDNSPrefetchDeferred() override;
+  virtual void OnDNSPrefetchRequested() override;
+  virtual bool HasDeferredDNSPrefetchRequest() override;
+
   // WebIDL
   bool Disabled();
   void SetDisabled(bool aDisabled);
   // XPCOM GetHref is fine.
   void SetHref(const nsAString& aHref, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::href, aHref, aRv);
   }
@@ -161,16 +168,19 @@ protected:
                                  nsAString& aType,
                                  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/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -606,23 +606,16 @@ nsXMLContentSink::CloseElement(nsIConten
         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);
           }
         }
-        if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
-          nsAutoString hrefVal;
-          aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
-          if (!hrefVal.IsEmpty()) {
-            PrefetchDNS(hrefVal);
-          }
-        }
       }
     }
   }
 
   return rv;
 }
 
 nsresult
--- a/parser/html/nsHtml5DocumentBuilder.cpp
+++ b/parser/html/nsHtml5DocumentBuilder.cpp
@@ -91,23 +91,16 @@ nsHtml5DocumentBuilder::UpdateStyleSheet
       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);
         }
       }
-      if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
-        nsAutoString hrefVal;
-        aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal);
-        if (!hrefVal.IsEmpty()) {
-          PrefetchDNS(hrefVal);
-        }
-      }
     }
   }
 
   // Re-open update
   BeginDocUpdate();
 }
 
 void