Bug 1406278: Part 2c - Use subject principal as triggering principal in <img> "srcset" attribute. r?bz
MozReview-Commit-ID: 784EsgwBcS1
--- 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 = {