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 274959 3254063d0c0a5fc412d0685dea8b9fef4b4bfa3c
parent 274958 4eb8f2d4c0262ed6870f9cd3f28b0de3d1c250b0
child 274960 f61e7d37cd1be5679c2ed507e7908dac93c5eb15
push id68732
push useropettay@mozilla.com
push dateTue, 01 Dec 2015 19:21:19 +0000
treeherdermozilla-inbound@3254063d0c0a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1134648
milestone45.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 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