Bug 1406278: Part 2c - Use subject principal as triggering principal in <img> "srcset" attribute. r?bz draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 02 Oct 2017 21:30:34 -0700
changeset 676197 cf85aa9c084a1f8721f3d9cd221a468062f74a8e
parent 676196 b4a1c44da84795b13cbf167dab7c19ab3a84bbd0
child 676198 a088266e0fdc6b63d9802f49acf8639de72acae4
push id83423
push usermaglione.k@gmail.com
push dateFri, 06 Oct 2017 20:33:12 +0000
reviewersbz
bugs1406278
milestone58.0a1
Bug 1406278: Part 2c - Use subject principal as triggering principal in <img> "srcset" attribute. r?bz MozReview-Commit-ID: 784EsgwBcS1
dom/base/ResponsiveImageSelector.cpp
dom/base/ResponsiveImageSelector.h
dom/html/HTMLImageElement.cpp
dom/html/HTMLImageElement.h
dom/webidl/HTMLImageElement.webidl
toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
--- a/dom/base/ResponsiveImageSelector.cpp
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -111,17 +111,18 @@ ResponsiveImageSelector::ResponsiveImage
 {
 }
 
 ResponsiveImageSelector::~ResponsiveImageSelector()
 {}
 
 // http://www.whatwg.org/specs/web-apps/current-work/#processing-the-image-candidates
 bool
-ResponsiveImageSelector::SetCandidatesFromSourceSet(const nsAString & aSrcSet)
+ResponsiveImageSelector::SetCandidatesFromSourceSet(const nsAString & aSrcSet,
+                                                    nsIPrincipal* aTriggeringPrincipal)
 {
   ClearSelectedCandidate();
 
   nsCOMPtr<nsIURI> docBaseURI = mOwnerNode ? mOwnerNode->GetBaseURI() : nullptr;
 
   if (!docBaseURI) {
     MOZ_ASSERT(false,
                "Should not be parsing SourceSet without a document");
@@ -163,16 +164,18 @@ ResponsiveImageSelector::SetCandidatesFr
 
     const nsDependentSubstring &urlStr = Substring(url, iter);
 
     MOZ_ASSERT(url != iter, "Shouldn't have empty URL at this point");
 
     ResponsiveImageCandidate candidate;
     if (candidate.ConsumeDescriptors(iter, end)) {
       candidate.SetURLSpec(urlStr);
+      candidate.SetTriggeringPrincipal(nsContentUtils::GetAttrTriggeringPrincipal(
+          Content(), urlStr, aTriggeringPrincipal));
       AppendCandidateIfUnique(candidate);
     }
   }
 
   bool parsedCandidates = mCandidates.Length() > 0;
 
   // Re-add default to end of list
   MaybeAppendDefaultCandidate();
@@ -203,28 +206,30 @@ ResponsiveImageSelector::Content()
 
 nsIDocument*
 ResponsiveImageSelector::Document()
 {
   return mOwnerNode->OwnerDoc();
 }
 
 void
-ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString)
+ResponsiveImageSelector::SetDefaultSource(const nsAString& aURLString,
+                                          nsIPrincipal* aPrincipal)
 {
   ClearSelectedCandidate();
 
   // Check if the last element of our candidates is a default
   int32_t candidates = mCandidates.Length();
   if (candidates && (mCandidates[candidates - 1].Type() ==
                      ResponsiveImageCandidate::eCandidateType_Default)) {
     mCandidates.RemoveElementAt(candidates - 1);
   }
 
   mDefaultSourceURL = aURLString;
+  mDefaultSourceTriggeringPrincipal = aPrincipal;
 
   // Add new default to end of list
   MaybeAppendDefaultCandidate();
 }
 
 void
 ResponsiveImageSelector::ClearSelectedCandidate()
 {
@@ -287,16 +292,17 @@ ResponsiveImageSelector::MaybeAppendDefa
     } else if (mCandidates[i].Density(this) == 1.0) {
       return;
     }
   }
 
   ResponsiveImageCandidate defaultCandidate;
   defaultCandidate.SetParameterDefault();
   defaultCandidate.SetURLSpec(mDefaultSourceURL);
+  defaultCandidate.SetTriggeringPrincipal(mDefaultSourceTriggeringPrincipal);
   // We don't use MaybeAppend since we want to keep this even if it can never
   // match, as it may if the source set changes.
   mCandidates.AppendElement(defaultCandidate);
 }
 
 already_AddRefed<nsIURI>
 ResponsiveImageSelector::GetSelectedImageURL()
 {
@@ -325,16 +331,27 @@ ResponsiveImageSelector::GetSelectedImag
   int bestIndex = GetSelectedCandidateIndex();
   if (bestIndex < 0) {
     return 1.0;
   }
 
   return mCandidates[bestIndex].Density(this);
 }
 
+nsIPrincipal*
+ResponsiveImageSelector::GetSelectedImageTriggeringPrincipal()
+{
+  int bestIndex = GetSelectedCandidateIndex();
+  if (bestIndex < 0) {
+    return nullptr;
+  }
+
+  return mCandidates[bestIndex].TriggeringPrincipal();
+}
+
 bool
 ResponsiveImageSelector::SelectImage(bool aReselect)
 {
   if (!aReselect && mSelectedCandidateIndex != -1) {
     // Already have selection
     return false;
   }
 
@@ -453,31 +470,39 @@ ResponsiveImageSelector::ComputeFinalWid
 
 ResponsiveImageCandidate::ResponsiveImageCandidate()
 {
   mType = eCandidateType_Invalid;
   mValue.mDensity = 1.0;
 }
 
 ResponsiveImageCandidate::ResponsiveImageCandidate(const nsAString& aURLString,
-                                                   double aDensity)
+                                                   double aDensity,
+                                                   nsIPrincipal* aTriggeringPrincipal)
   : mURLString(aURLString)
+  , mTriggeringPrincipal(aTriggeringPrincipal)
 {
   mType = eCandidateType_Density;
   mValue.mDensity = aDensity;
 }
 
 
 void
 ResponsiveImageCandidate::SetURLSpec(const nsAString& aURLString)
 {
   mURLString = aURLString;
 }
 
 void
+ResponsiveImageCandidate::SetTriggeringPrincipal(nsIPrincipal* aPrincipal)
+{
+  mTriggeringPrincipal = aPrincipal;
+}
+
+void
 ResponsiveImageCandidate::SetParameterAsComputedWidth(int32_t aWidth)
 {
   mType = eCandidateType_ComputedFromWidth;
   mValue.mWidth = aWidth;
 }
 
 void
 ResponsiveImageCandidate::SetParameterDefault()
@@ -713,16 +738,22 @@ ResponsiveImageCandidate::HasSameParamet
 }
 
 const nsAString&
 ResponsiveImageCandidate::URLString() const
 {
   return mURLString;
 }
 
+nsIPrincipal*
+ResponsiveImageCandidate::TriggeringPrincipal() const
+{
+  return mTriggeringPrincipal;
+}
+
 double
 ResponsiveImageCandidate::Density(ResponsiveImageSelector *aSelector) const
 {
   if (mType == eCandidateType_ComputedFromWidth) {
     double width;
     if (!aSelector->ComputeFinalWidthForCurrentViewport(&width)) {
       return 1.0;
     }
--- a/dom/base/ResponsiveImageSelector.h
+++ b/dom/base/ResponsiveImageSelector.h
@@ -40,24 +40,26 @@ public:
   //
   // Because the selected image depends on external variants like
   // viewport size and device pixel ratio, the time at which image
   // selection occurs can affect the result.
 
 
   // Given a srcset string, parse and replace current candidates (does not
   // replace default source)
-  bool SetCandidatesFromSourceSet(const nsAString & aSrcSet);
+  bool SetCandidatesFromSourceSet(const nsAString & aSrcSet,
+                                  nsIPrincipal* aTriggeringPrincipal = nullptr);
 
   // Fill the source sizes from a valid sizes descriptor. Returns false if
   // descriptor is invalid.
   bool SetSizesFromDescriptor(const nsAString & aSizesDescriptor);
 
   // Set the default source, treated as the least-precedence 1.0 density source.
-  void SetDefaultSource(const nsAString& aURLString);
+  void SetDefaultSource(const nsAString& aURLString,
+                        nsIPrincipal* aPrincipal = nullptr);
 
   uint32_t NumCandidates(bool aIncludeDefault = true);
 
   // If this was created for a specific content. May be null if we were only
   // created for a document.
   nsIContent *Content();
 
   // The document we were created for, or the owner document of the content if
@@ -65,16 +67,17 @@ public:
   nsIDocument *Document();
 
   // Get the url and density for the selected best candidate. These
   // implicitly cause an image to be selected if necessary.
   already_AddRefed<nsIURI> GetSelectedImageURL();
   // Returns false if there is no selected image
   bool GetSelectedImageURLSpec(nsAString& aResult);
   double GetSelectedImageDensity();
+  nsIPrincipal* GetSelectedImageTriggeringPrincipal();
 
   // Runs image selection now if necessary. If an image has already
   // been choosen, takes no action unless aReselect is true.
   //
   // aReselect - Always re-run selection, replacing the previously
   //             choosen image.
   // return - true if the selected image result changed.
   bool SelectImage(bool aReselect = false);
@@ -103,34 +106,37 @@ private:
   //
   // aContext is the presContext to use for current viewport sizing, null will
   // use the associated content's context.
   bool ComputeFinalWidthForCurrentViewport(double* aWidth);
 
   nsCOMPtr<nsINode> mOwnerNode;
   // The cached URL for default candidate.
   nsString mDefaultSourceURL;
+  nsCOMPtr<nsIPrincipal> mDefaultSourceTriggeringPrincipal;
   // If this array contains an eCandidateType_Default, it should be the last
   // element, such that the Setters can preserve/replace it respectively.
   nsTArray<ResponsiveImageCandidate> mCandidates;
   int mSelectedCandidateIndex;
   // The cached resolved URL for mSelectedCandidateIndex, such that we only
   // resolve the absolute URL at selection time
   nsCOMPtr<nsIURI> mSelectedCandidateURL;
 
   nsTArray< nsAutoPtr<nsMediaQuery> > mSizeQueries;
   nsTArray<nsCSSValue> mSizeValues;
 };
 
 class ResponsiveImageCandidate {
 public:
   ResponsiveImageCandidate();
-  ResponsiveImageCandidate(const nsAString& aURLString, double aDensity);
+  ResponsiveImageCandidate(const nsAString& aURLString, double aDensity,
+                           nsIPrincipal* aTriggeringPrincipal = nullptr);
 
   void SetURLSpec(const nsAString& aURLString);
+  void SetTriggeringPrincipal(nsIPrincipal* aPrincipal);
   // Set this as a default-candidate. This behaves the same as density 1.0, but
   // has a differing type such that it can be replaced by subsequent
   // SetDefaultSource calls.
   void SetParameterDefault();
 
   // Set this candidate as a by-density candidate with specified density.
   void SetParameterAsDensity(double aDensity);
   void SetParameterAsComputedWidth(int32_t aWidth);
@@ -143,16 +149,17 @@ public:
   // of descriptors microsyntax.
   bool ConsumeDescriptors(nsAString::const_iterator& aIter,
                           const nsAString::const_iterator& aIterEnd);
 
   // Check if our parameter (which does not include the url) is identical
   bool HasSameParameter(const ResponsiveImageCandidate & aOther) const;
 
   const nsAString& URLString() const;
+  nsIPrincipal* TriggeringPrincipal() const;
 
   // Compute and return the density relative to a selector.
   double Density(ResponsiveImageSelector *aSelector) const;
   // If the width is already known. Useful when iterating over candidates to
   // avoid having each call re-compute the width.
   double Density(double aMatchingWidth) const;
 
   // If this selector is computed from the selector's matching width.
@@ -167,16 +174,17 @@ public:
     eCandidateType_ComputedFromWidth
   };
 
   eCandidateType Type() const { return mType; }
 
 private:
 
   nsString mURLString;
+  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
   eCandidateType mType;
   union {
     double mDensity;
     int32_t mWidth;
   } mValue;
 };
 
 } // namespace dom
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -363,16 +363,18 @@ HTMLImageElement::AfterSetAttr(int32_t a
       CancelImageRequests(aNotify);
     }
   } else if (aName == nsGkAtoms::srcset &&
              aNameSpaceID == kNameSpaceID_None) {
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
     mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
 
+    mSrcsetTriggeringPrincipal = aSubjectPrincipal;
+
     PictureSourceSrcsetChanged(this, attrVal.String(), aNotify);
   } else if (aName == nsGkAtoms::sizes &&
              aNameSpaceID == kNameSpaceID_None) {
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
     mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
 
     PictureSourceSizesChanged(this, attrVal.String(), aNotify);
@@ -419,17 +421,18 @@ HTMLImageElement::AfterMaybeChangeAttr(i
     mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
 
     mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
         this, aValue.String(), aSubjectPrincipal);
 
     if (InResponsiveMode()) {
       if (mResponsiveSelector &&
           mResponsiveSelector->Content() == this) {
-        mResponsiveSelector->SetDefaultSource(aValue.String());
+        mResponsiveSelector->SetDefaultSource(aValue.String(),
+                                              mSrcTriggeringPrincipal);
       }
       QueueImageLoadTask(true);
     } else if (aNotify && OwnerDoc()->IsCurrentActiveDocument()) {
       // If aNotify is false, we are coming from the parser or some such place;
       // we'll get bound after all the attributes have been set, so we'll do the
       // sync image load from BindToTree. Skip the LoadImage call in that case.
 
       // Note that this sync behavior is partially removed from the spec, bug 1076583
@@ -974,23 +977,25 @@ HTMLImageElement::LoadSelectedImage(bool
       return NS_OK;
     }
   }
 
   nsCOMPtr<nsIURI> selectedSource;
   double currentDensity = 1.0; // default to 1.0 for the src attribute case
   if (mResponsiveSelector) {
     nsCOMPtr<nsIURI> url = mResponsiveSelector->GetSelectedImageURL();
+    nsCOMPtr<nsIPrincipal> triggeringPrincipal = mResponsiveSelector->GetSelectedImageTriggeringPrincipal();
     selectedSource = url;
     currentDensity = mResponsiveSelector->GetSelectedImageDensity();
     if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource, currentDensity)) {
       return NS_OK;
     }
     if (url) {
-      rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset);
+      rv = LoadImage(url, aForce, aNotify, eImageLoadType_Imageset,
+                     triggeringPrincipal);
     }
   } else {
     nsAutoString src;
     if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
       CancelImageRequests(aNotify);
       rv = NS_OK;
     } else {
       nsIDocument* doc = GetOurOwnerDoc();
@@ -1029,17 +1034,21 @@ HTMLImageElement::PictureSourceSrcsetCha
              "Should not be getting notifications for non-previous-siblings");
 
   nsIContent *currentSrc =
     mResponsiveSelector ? mResponsiveSelector->Content() : nullptr;
 
   if (aSourceNode == currentSrc) {
     // We're currently using this node as our responsive selector
     // source.
-    mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue);
+    nsCOMPtr<nsIPrincipal> principal;
+    if (aSourceNode == this) {
+      principal = mSrcsetTriggeringPrincipal;
+    }
+    mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue, principal);
   }
 
   if (!mInDocResponsiveContent && IsInComposedDoc()) {
     nsIDocument* doc = GetOurOwnerDoc();
     if (doc) {
       doc->AddResponsiveContent(this);
       mInDocResponsiveContent = true;
     }
@@ -1214,55 +1223,58 @@ HTMLImageElement::SourceElementMatches(n
   }
 
   return true;
 }
 
 bool
 HTMLImageElement::TryCreateResponsiveSelector(nsIContent *aSourceNode)
 {
+  nsCOMPtr<nsIPrincipal> principal;
+
   // Skip if this is not a <source> with matching media query
   bool isSourceTag = aSourceNode->IsHTMLElement(nsGkAtoms::source);
   if (isSourceTag) {
     if (!SourceElementMatches(aSourceNode)) {
       return false;
     }
   } else if (aSourceNode->IsHTMLElement(nsGkAtoms::img)) {
     // Otherwise this is the <img> tag itself
     MOZ_ASSERT(aSourceNode == this);
+    principal = mSrcsetTriggeringPrincipal;
   }
 
   // Skip if has no srcset or an empty srcset
   nsString srcset;
   if (!aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::srcset, srcset)) {
     return false;
   }
 
   if (srcset.IsEmpty()) {
     return false;
   }
 
 
   // Try to parse
   RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aSourceNode);
-  if (!sel->SetCandidatesFromSourceSet(srcset)) {
+  if (!sel->SetCandidatesFromSourceSet(srcset, principal)) {
     // No possible candidates, don't need to bother parsing sizes
     return false;
   }
 
   nsAutoString sizes;
   aSourceNode->GetAttr(kNameSpaceID_None, nsGkAtoms::sizes, sizes);
   sel->SetSizesFromDescriptor(sizes);
 
   // If this is the <img> tag, also pull in src as the default source
   if (!isSourceTag) {
     MOZ_ASSERT(aSourceNode == this);
     nsAutoString src;
     if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) && !src.IsEmpty()) {
-      sel->SetDefaultSource(src);
+      sel->SetDefaultSource(src, mSrcTriggeringPrincipal);
     }
   }
 
   mResponsiveSelector = sel;
   return true;
 }
 
 /* static */ bool
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -145,23 +145,23 @@ public:
   void GetSrc(nsAString& aSrc, nsIPrincipal&)
   {
     GetURIAttr(nsGkAtoms::src, nullptr, aSrc);
   }
   void SetSrc(const nsAString& aSrc, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::src, aSrc, aSubjectPrincipal, aError);
   }
-  void GetSrcset(nsAString& aSrcset)
+  void GetSrcset(nsAString& aSrcset, nsIPrincipal&)
   {
     GetHTMLAttr(nsGkAtoms::srcset, aSrcset);
   }
-  void SetSrcset(const nsAString& aSrcset, ErrorResult& aError)
+  void SetSrcset(const nsAString& aSrcset, nsIPrincipal& aSubjectPrincipal, ErrorResult& aError)
   {
-    SetHTMLAttr(nsGkAtoms::srcset, aSrcset, aError);
+    SetHTMLAttr(nsGkAtoms::srcset, aSrcset, aSubjectPrincipal, aError);
   }
   void GetCrossOrigin(nsAString& aResult)
   {
     // Null for both missing and invalid defaults is ok, since we
     // always parse to an enum value, so we don't need an invalid
     // default, and we _want_ the missing default to be null.
     GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
   }
@@ -422,16 +422,17 @@ private:
                             const nsAttrValueOrString& aValue,
                             const nsAttrValue* aOldValue,
                             nsIPrincipal* aSubjectPrincipal,
                             bool aValueMaybeChanged, bool aNotify);
 
   bool mInDocResponsiveContent;
   RefPtr<ImageLoadTask> mPendingImageLoadTask;
   nsCOMPtr<nsIPrincipal> mSrcTriggeringPrincipal;
+  nsCOMPtr<nsIPrincipal> mSrcsetTriggeringPrincipal;
 
   // Last URL that was attempted to load by this element.
   nsCOMPtr<nsIURI> mLastSelectedSource;
   // Last pixel density that was selected.
   double mCurrentDensity;
 };
 
 } // namespace dom
--- a/dom/webidl/HTMLImageElement.webidl
+++ b/dom/webidl/HTMLImageElement.webidl
@@ -18,17 +18,17 @@ interface nsIStreamListener;
 
 [HTMLConstructor,
  NamedConstructor=Image(optional unsigned long width, optional unsigned long height)]
 interface HTMLImageElement : HTMLElement {
            [CEReactions, SetterThrows]
            attribute DOMString alt;
            [CEReactions, NeedsSubjectPrincipal, SetterThrows]
            attribute DOMString src;
-           [CEReactions, SetterThrows]
+           [CEReactions, NeedsSubjectPrincipal, SetterThrows]
            attribute DOMString srcset;
            [CEReactions, SetterThrows]
            attribute DOMString? crossOrigin;
            [CEReactions, SetterThrows]
            attribute DOMString useMap;
            [CEReactions, SetterThrows]
            attribute DOMString referrerPolicy;
            [CEReactions, SetterThrows]
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
@@ -425,16 +425,21 @@ add_task(async function test_contentscri
    * A list of tests to run in each context, as understood by
    * {@see getElementData}.
    */
   const TESTS = [
     {
       element: ["img", {}],
       src: "img.png",
     },
+    {
+      element: ["img", {}],
+      src: "imgset.png",
+      srcAttr: "srcset",
+    },
   ];
 
   /**
    * A set of sources for which each of the above tests is expected to
    * generate one request, if each of the properties in the value object
    * matches the value of the same property in the test object.
    */
   const SOURCES = {