Bug 1067345 - Part 2 - Add HTMLImageElement::SelectSourceForTagWithAttrs to do <picture> source selection without a DOM
authorJohn Schoenick <jschoenick@mozilla.com>
Wed, 10 Dec 2014 18:53:00 -0500
changeset 229466 c1b3abf6bb988443bf0670035219ec4c585e0bee
parent 229465 406045c02f899cfc99bebb279bafb6d1c9860e3c
child 229467 b64c085b06e1446de958cbd0186d2900c50026fc
push id28287
push userryanvm@gmail.com
push dateTue, 17 Feb 2015 20:08:22 +0000
treeherdermozilla-central@b6c56fab513d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1067345
milestone38.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 1067345 - Part 2 - Add HTMLImageElement::SelectSourceForTagWithAttrs to do <picture> source selection without a DOM
dom/html/HTMLImageElement.cpp
dom/html/HTMLImageElement.h
dom/html/HTMLSourceElement.cpp
dom/html/HTMLSourceElement.h
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -1084,16 +1084,24 @@ HTMLImageElement::UpdateResponsiveSource
   }
 
   if (!candidateSource) {
     // Ran out of siblings without finding ourself, e.g. XBL magic.
     mResponsiveSelector = nullptr;
   }
 }
 
+/*static */ bool
+HTMLImageElement::SupportedPictureSourceType(const nsAString& aType)
+{
+  return
+    imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(aType).get(),
+                                        AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
+}
+
 bool
 HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode,
                                               const nsAString *aSrcset,
                                               const nsAString *aSizes)
 {
   if (!IsSrcsetEnabled()) {
     return false;
   }
@@ -1110,20 +1118,17 @@ HTMLImageElement::TryCreateResponsiveSel
     // Check media and type
     HTMLSourceElement *src = static_cast<HTMLSourceElement*>(aSourceNode);
     if (!src->MatchesCurrentMedia()) {
       return false;
     }
 
     nsAutoString type;
     if (aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
-        !imgLoader::SupportImageWithMimeType(
-          NS_ConvertUTF16toUTF8(type).get(),
-          AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)
-        ) {
+        !SupportedPictureSourceType(type)) {
       return false;
     }
   } else if (aSourceNode->Tag() == nsGkAtoms::img) {
     // Otherwise this is the <img> tag itself
     MOZ_ASSERT(aSourceNode == this);
   }
 
   // Skip if has no srcset or an empty srcset
@@ -1163,16 +1168,81 @@ HTMLImageElement::TryCreateResponsiveSel
       sel->SetDefaultSource(src);
     }
   }
 
   mResponsiveSelector = sel;
   return true;
 }
 
+/* static */ bool
+HTMLImageElement::SelectSourceForTagWithAttrs(nsIDocument *aDocument,
+                                              bool aIsSourceTag,
+                                              const nsAString& aSrcAttr,
+                                              const nsAString& aSrcsetAttr,
+                                              const nsAString& aSizesAttr,
+                                              const nsAString& aTypeAttr,
+                                              const nsAString& aMediaAttr,
+                                              nsAString& aResult)
+{
+  MOZ_ASSERT(aIsSourceTag || (aTypeAttr.IsEmpty() && aMediaAttr.IsEmpty()),
+             "Passing type or media attrs makes no sense without aIsSourceTag");
+  MOZ_ASSERT(!aIsSourceTag || aSrcAttr.IsEmpty(),
+             "Passing aSrcAttr makes no sense with aIsSourceTag set");
+
+  bool pictureEnabled = HTMLPictureElement::IsPictureEnabled();
+  if (aIsSourceTag && !pictureEnabled) {
+    return false;
+  }
+
+  if (!IsSrcsetEnabled() || aSrcsetAttr.IsEmpty()) {
+    if (!aIsSourceTag) {
+      // For an <img> with no srcset, we would always select the src attr.
+      aResult.Assign(aSrcAttr);
+      return true;
+    }
+    // Otherwise, a <source> without srcset is never selected
+    return false;
+  }
+
+  // Would not consider source tags with unsupported media or type
+  if (aIsSourceTag &&
+      ((!aMediaAttr.IsVoid() &&
+       !HTMLSourceElement::WouldMatchMediaForDocument(aMediaAttr, aDocument)) ||
+      (!aTypeAttr.IsVoid() &&
+       !SupportedPictureSourceType(aTypeAttr)))) {
+    return false;
+  }
+
+  // Using srcset or picture <source>, build a responsive selector for this tag.
+  nsRefPtr<ResponsiveImageSelector> sel =
+    new ResponsiveImageSelector(aDocument);
+
+  sel->SetCandidatesFromSourceSet(aSrcsetAttr);
+  if (pictureEnabled && !aSizesAttr.IsEmpty()) {
+    sel->SetSizesFromDescriptor(aSizesAttr);
+  }
+  if (!aIsSourceTag) {
+    sel->SetDefaultSource(aSrcAttr);
+  }
+
+  if (sel->GetSelectedImageURLSpec(aResult)) {
+    return true;
+  }
+
+  if (!aIsSourceTag) {
+    // <img> tag with no match would definitively load nothing.
+    aResult.Truncate();
+    return true;
+  }
+
+  // <source> tags with no match would leave source yet-undetermined.
+  return false;
+}
+
 void
 HTMLImageElement::DestroyContent()
 {
   mResponsiveSelector = nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -200,16 +200,59 @@ public:
 #ifdef DEBUG
   nsIDOMHTMLFormElement* GetForm() const;
 #endif
   void SetForm(nsIDOMHTMLFormElement* aForm);
   void ClearForm(bool aRemoveFromForm);
 
   virtual void DestroyContent() MOZ_OVERRIDE;
 
+  /**
+   * Given a hypothetical <img> or <source> tag with the given parameters,
+   * return what URI we would attempt to use, if any.  Used by the preloader to
+   * resolve sources prior to DOM creation.
+   *
+   * @param aDocument The document this image would be for, for referencing
+   *        viewport width and DPI/zoom
+   * @param aIsSourceTag If these parameters are for a <source> tag (as in a
+   *        <picture>) rather than an <img> tag. Note that some attrs are unused
+   *        when this is true an vice versa
+   * @param aSrcAttr [ignored if aIsSourceTag] The src attr for this image.
+   * @param aSrcsetAttr The srcset attr for this image/source
+   * @param aSizesAttr The sizes attr for this image/source
+   * @param aTypeAttr [ignored if !aIsSourceTag] The type attr for this source.
+   *                  Should be a void string to differentiate no type attribute
+   *                  from an empty one.
+   * @param aMediaAttr [ignored if !aIsSourceTag] The media attr for this
+   *                   source.  Should be a void string to differentiate no
+   *                   media attribute from an empty one.
+   * @param aResult A reference to store the resulting URL spec in if we
+   *                selected a source.  This value is not guaranteed to parse to
+   *                a valid URL, merely the URL that the tag would attempt to
+   *                resolve and load (which may be the empty string).  This
+   *                parameter is not modified if return value is false.
+   * @return True if we were able to select a final source, false if further
+   *         sources would be considered.  It follows that this always returns
+   *         true if !aIsSourceTag.
+   *
+   * Note that the return value may be true with an empty string as the result,
+   * which implies that the parameters provided describe a tag that would select
+   * no source.  This is distinct from a return of false which implies that
+   * further <source> or <img> tags would be considered.
+   */
+  static bool
+    SelectSourceForTagWithAttrs(nsIDocument *aDocument,
+                                bool aIsSourceTag,
+                                const nsAString& aSrcAttr,
+                                const nsAString& aSrcsetAttr,
+                                const nsAString& aSizesAttr,
+                                const nsAString& aTypeAttr,
+                                const nsAString& aMediaAttr,
+                                nsAString& aResult);
+
 protected:
   virtual ~HTMLImageElement();
 
   // Queues a task to run LoadSelectedImage pending stable state.
   //
   // Pending Bug 1076583 this is only used by the responsive image
   // algorithm (InResponsiveMode()) -- synchronous actions when just
   // using img.src will bypass this, and update source and kick off
@@ -223,16 +266,19 @@ protected:
   // True if we are using the newer image loading algorithm. This will be the
   // only mode after Bug 1076583
   bool InResponsiveMode();
 
   // Resolve and load the current mResponsiveSelector (responsive mode) or src
   // attr image.
   nsresult LoadSelectedImage(bool aForce, bool aNotify);
 
+  // True if this string represents a type we would support on <source type>
+  static bool SupportedPictureSourceType(const nsAString& aType);
+
   // Update/create/destroy mResponsiveSelector
   void PictureSourceSrcsetChanged(nsIContent *aSourceNode,
                                   const nsAString& aNewValue, bool aNotify);
   void PictureSourceSizesChanged(nsIContent *aSourceNode,
                                  const nsAString& aNewValue, bool aNotify);
   // As we re-run the source selection on these mutations regardless,
   // we don't actually care which changed or to what
   void PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode, bool aNotify);
--- a/dom/html/HTMLSourceElement.cpp
+++ b/dom/html/HTMLSourceElement.cpp
@@ -50,16 +50,35 @@ HTMLSourceElement::MatchesCurrentMedia()
     nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
     return pctx && mMediaList->Matches(pctx, nullptr);
   }
 
   // No media specified
   return true;
 }
 
+/* static */ bool
+HTMLSourceElement::WouldMatchMediaForDocument(const nsAString& aMedia,
+                                              const nsIDocument *aDocument)
+{
+  if (aMedia.IsEmpty()) {
+    return true;
+  }
+
+  nsIPresShell* presShell = aDocument->GetShell();
+  nsPresContext* pctx = presShell ? presShell->GetPresContext() : nullptr;
+  MOZ_ASSERT(pctx, "Called for document with no prescontext");
+
+  nsCSSParser cssParser;
+  nsRefPtr<nsMediaList> mediaList = new nsMediaList();
+  cssParser.ParseMediaList(aMedia, nullptr, 0, mediaList, false);
+
+  return pctx && mediaList->Matches(pctx, nullptr);
+}
+
 nsresult
 HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify)
 {
   // If we are associated with a <picture> with a valid <img>, notify it of
   // responsive parameter changes
   nsINode *parent = nsINode::GetParentNode();
   if (aNameSpaceID == kNameSpaceID_None &&
--- a/dom/html/HTMLSourceElement.h
+++ b/dom/html/HTMLSourceElement.h
@@ -39,16 +39,22 @@ public:
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
 
   // If this element's media attr matches for its owner document.  Returns true
   // if no media attr was set.
   bool MatchesCurrentMedia();
 
+  // True if a source tag would match the given media attribute for the
+  // specified document. Used by the preloader to determine valid <source> tags
+  // prior to DOM creation.
+  static bool WouldMatchMediaForDocument(const nsAString& aMediaStr,
+                                         const nsIDocument *aDocument);
+
   // WebIDL
   void GetSrc(nsString& aSrc)
   {
     GetURIAttr(nsGkAtoms::src, nullptr, aSrc);
   }
   void SetSrc(const nsAString& aSrc, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::src, aSrc, rv);