Bug 1166910 - Referrer attribute for img tag. r=ckerschb, r=hsivonen, r=bz
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Fri, 05 Jun 2015 15:25:24 -0700
changeset 251206 78d7963ccfb0fda2197db7736263fd9094502d55
parent 251205 a48456e8eaa5627fa8896697c7c1536de6cc8d7d
child 251207 3fae1257ad4ea7f348885b38310d65a06908ed65
push id28987
push userkwierso@gmail.com
push dateFri, 03 Jul 2015 00:14:52 +0000
treeherdermozilla-central@1af1b4e1c35a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, hsivonen, bz
bugs1166910
milestone42.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 1166910 - Referrer attribute for img tag. r=ckerschb, r=hsivonen, r=bz
dom/base/nsImageLoadingContent.cpp
dom/base/nsImageLoadingContent.h
dom/html/HTMLImageElement.h
dom/html/nsGenericHTMLElement.cpp
dom/html/nsGenericHTMLElement.h
dom/webidl/HTMLImageElement.webidl
image/imgLoader.cpp
modules/libpref/init/all.js
netwerk/base/ReferrerPolicy.h
parser/html/nsHtml5SpeculativeLoad.cpp
parser/html/nsHtml5SpeculativeLoad.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -41,16 +41,17 @@
 #include "nsIContentPolicy.h"
 #include "nsSVGEffects.h"
 
 #include "mozAutoDocUpdate.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Preferences.h"
 
 #ifdef LoadImage
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 using namespace mozilla;
 
@@ -925,25 +926,37 @@ nsImageLoadingContent::LoadImage(nsIURI*
   nsLoadFlags loadFlags = aLoadFlags;
   int32_t corsmode = GetCORSMode();
   if (corsmode == CORS_ANONYMOUS) {
     loadFlags |= imgILoader::LOAD_CORS_ANONYMOUS;
   } else if (corsmode == CORS_USE_CREDENTIALS) {
     loadFlags |= imgILoader::LOAD_CORS_USE_CREDENTIALS;
   }
 
+  // get document wide referrer policy
+  mozilla::net::ReferrerPolicy referrerPolicy = aDocument->GetReferrerPolicy();
+  bool referrerAttributeEnabled = Preferences::GetBool("network.http.enablePerElementReferrer", false);
+  // if referrer attributes are enabled in preferences, load img referrer attribute
+  nsresult rv;
+  if (referrerAttributeEnabled) {
+    mozilla::net::ReferrerPolicy imgReferrerPolicy = GetImageReferrerPolicy();
+    // if the image does not provide a referrer attribute, ignore this
+    if (imgReferrerPolicy != mozilla::net::RP_Unset) {
+      referrerPolicy = imgReferrerPolicy;
+    }
+  }
+
   // Not blocked. Do the load.
   nsRefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType);
   nsCOMPtr<nsIContent> content =
       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  nsresult rv;
   rv = nsContentUtils::LoadImage(aNewURI, aDocument,
                                  aDocument->NodePrincipal(),
                                  aDocument->GetDocumentURI(),
-                                 aDocument->GetReferrerPolicy(),
+                                 referrerPolicy,
                                  this, loadFlags,
                                  content->LocalName(),
                                  getter_AddRefs(req),
                                  policyType);
 
   // Tell the document to forget about the image preload, if any, for
   // this URI, now that we might have another imgRequestProxy for it.
   // That way if we get canceled later the image load won't continue.
@@ -1561,8 +1574,16 @@ nsImageLoadingContent::ImageObserver::Im
   MOZ_COUNT_CTOR(ImageObserver);
 }
 
 nsImageLoadingContent::ImageObserver::~ImageObserver()
 {
   MOZ_COUNT_DTOR(ImageObserver);
   NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
 }
+
+// Only HTMLInputElement.h overrides this for <img> tags
+// all other subclasses use this one, i.e. ignore referrer attributes
+mozilla::net::ReferrerPolicy
+nsImageLoadingContent::GetImageReferrerPolicy()
+{
+  return mozilla::net::RP_Unset;
+};
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -19,16 +19,17 @@
 #include "mozilla/EventStates.h"
 #include "nsCOMPtr.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIRequest.h"
 #include "mozilla/ErrorResult.h"
 #include "nsAutoPtr.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 class nsIURI;
 class nsIDocument;
 class nsPresContext;
 class nsIContent;
 class imgRequestProxy;
 
 #ifdef LoadImage
@@ -193,16 +194,18 @@ protected:
   void ClearBrokenState() { mBroken = false; }
 
   /**
    * Returns the CORS mode that will be used for all future image loads. The
    * default implementation returns CORS_NONE unconditionally.
    */
   virtual mozilla::CORSMode GetCORSMode();
 
+  virtual mozilla::net::ReferrerPolicy GetImageReferrerPolicy();
+
   // Subclasses are *required* to call BindToTree/UnbindFromTree.
   void BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                   nsIContent* aBindingParent, bool aCompileEventHandlers);
   void UnbindFromTree(bool aDeep, bool aNullParent);
 
   nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
   void OnUnlockedDraw();
   nsresult OnImageIsAnimated(imgIRequest *aRequest);
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -184,16 +184,30 @@ public:
   void SetSizes(const nsAString& aSizes, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::sizes, aSizes, aError);
   }
   void SetBorder(const nsAString& aBorder, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::border, aBorder, aError);
   }
+  void SetReferrer(const nsAString& aReferrer, ErrorResult& aError)
+  {
+    SetHTMLAttr(nsGkAtoms::referrer, aReferrer, aError);
+  }
+  void GetReferrer(nsAString& aReferrer)
+  {
+    GetEnumAttr(nsGkAtoms::referrer, nullptr, aReferrer);
+  }
+
+  mozilla::net::ReferrerPolicy
+  GetImageReferrerPolicy()
+  {
+    return GetReferrerPolicy();
+  }
 
   int32_t X();
   int32_t Y();
   // Uses XPCOM GetLowsrc.
   void SetLowsrc(const nsAString& aLowsrc, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::lowsrc, aLowsrc, aError);
   }
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -101,16 +101,18 @@
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/ErrorResult.h"
 #include "nsHTMLDocument.h"
 #include "nsGlobalWindow.h"
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "imgIContainer.h"
 
+#include "mozilla/net/ReferrerPolicy.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 /**
  * nsAutoFocusEvent is used to dispatch a focus event when a
  * nsGenericHTMLFormElement is binded to the tree with the autofocus attribute
  * enabled.
  */
@@ -990,16 +992,20 @@ nsGenericHTMLElement::ParseAttribute(int
     if (aAttribute == nsGkAtoms::dir) {
       return aResult.ParseEnumValue(aValue, kDirTable, false);
     }
   
     if (aAttribute == nsGkAtoms::tabindex) {
       return aResult.ParseIntValue(aValue);
     }
 
+    if (aAttribute == nsGkAtoms::referrer) {
+      return ParseReferrerAttribute(aValue, aResult);
+    }
+
     if (aAttribute == nsGkAtoms::name) {
       // Store name as an atom.  name="" means that the element has no name,
       // not that it has an emptystring as the name.
       RemoveFromNameTable();
       if (aValue.IsEmpty()) {
         ClearHasName();
         return false;
       }
@@ -1258,16 +1264,29 @@ nsGenericHTMLElement::ParseImageAttribut
       (aAttribute == nsGkAtoms::vspace) ||
       (aAttribute == nsGkAtoms::border)) {
     return aResult.ParseIntWithBounds(aString, 0);
   }
   return false;
 }
 
 bool
+nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString,
+                                             nsAttrValue& aResult)
+{
+  static const nsAttrValue::EnumTable kReferrerTable[] = {
+    { "no-referrer", net::RP_No_Referrer },
+    { "origin", net::RP_Origin },
+    { "unsafe-url", net::RP_Unsafe_URL },
+    { 0 }
+  };
+  return aResult.ParseEnumValue(aString, kReferrerTable, false);
+}
+
+bool
 nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString,
                                             nsAttrValue& aResult)
 {
   return aResult.ParseEnumValue(aString, kFrameborderTable, false);
 }
 
 bool
 nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString,
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -228,16 +228,27 @@ public:
   {
     return mScrollgrab;
   }
   void SetScrollgrab(bool aValue)
   {
     mScrollgrab = aValue;
   }
 
+  mozilla::net::ReferrerPolicy
+  GetReferrerPolicy()
+  {
+    nsAutoString aPolicyString;
+    GetEnumAttr(nsGkAtoms::referrer, nullptr, aPolicyString);
+    if (aPolicyString.IsEmpty()) {
+      return mozilla::net::RP_Unset;
+    }
+    return mozilla::net::ReferrerPolicyFromString(aPolicyString);
+  }
+
   /**
    * Determine whether an attribute is an event (onclick, etc.)
    * @param aName the attribute
    * @return whether the name is an event handler name
    */
   virtual bool IsEventAttributeName(nsIAtom* aName) override;
 
 #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
@@ -706,16 +717,20 @@ public:
    * @param aAttribute the attribute to parse
    * @param aString the string to parse
    * @param aResult the resulting HTMLValue
    * @return whether the value was parsed
    */
   static bool ParseImageAttribute(nsIAtom* aAttribute,
                                     const nsAString& aString,
                                     nsAttrValue& aResult);
+
+  static bool ParseReferrerAttribute(const nsAString& aString,
+                                     nsAttrValue& aResult);
+
   /**
    * Convert a frameborder string to value (yes/no/1/0)
    *
    * @param aString the string to parse
    * @param aResult the resulting HTMLValue
    * @return whether the value was parsed
    */
   static bool ParseFrameborderValue(const nsAString& aString,
--- a/dom/webidl/HTMLImageElement.webidl
+++ b/dom/webidl/HTMLImageElement.webidl
@@ -25,16 +25,18 @@ interface HTMLImageElement : HTMLElement
            attribute DOMString src;
            [SetterThrows, Pref="dom.image.srcset.enabled"]
            attribute DOMString srcset;
            [SetterThrows]
            attribute DOMString? crossOrigin;
            [SetterThrows]
            attribute DOMString useMap;
            [SetterThrows]
+           attribute DOMString referrer;
+           [SetterThrows]
            attribute boolean isMap;
            [SetterThrows]
            attribute unsigned long width;
            [SetterThrows]
            attribute unsigned long height;
   readonly attribute unsigned long naturalWidth;
   readonly attribute unsigned long naturalHeight;
   readonly attribute boolean complete;
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -665,16 +665,19 @@ ShouldLoadCachedImage(imgRequest* aImgRe
 // to CORS.  Also checks the Referrer Policy, since requests with different
 // referrers/policies may generate different responses.
 static bool
 ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck,
                      int32_t corsmode, nsIPrincipal* loadingPrincipal,
                      nsISupports* aCX, ReferrerPolicy referrerPolicy)
 {
   // If the entry's Referrer Policy doesn't match, we can't use this request.
+  // XXX: this will return false if an image has different referrer attributes,
+  // i.e. we currently don't use the cached image but reload the image with
+  // the new referrer policy bug 1174921
   if (referrerPolicy != request->GetReferrerPolicy()) {
     return false;
   }
 
   // If the entry's CORS mode doesn't match, or the CORS mode matches but the
   // document principal isn't the same, we can't use this request.
   if (request->GetCORSMode() != corsmode) {
     return false;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1269,16 +1269,19 @@ pref("network.http.referer.spoofSource",
 pref("network.http.referer.trimmingPolicy", 0);
 // 0=always send, 1=send iff base domains match, 2=send iff hosts match
 pref("network.http.referer.XOriginPolicy", 0);
 
 // Controls whether we send HTTPS referres to other HTTPS sites.
 // By default this is enabled for compatibility (see bug 141641)
 pref("network.http.sendSecureXSiteReferrer", true);
 
+// Controls whether referrer attributes in <a>, <img>, <area>, and <iframe> are honoured
+pref("network.http.enablePerElementReferrer", false);
+
 // Maximum number of consecutive redirects before aborting.
 pref("network.http.redirection-limit", 20);
 
 // Enable http compression: comment this out in case of problems with 1.1
 // NOTE: support for "compress" has been disabled per bug 196406.
 // NOTE: separate values with comma+space (", "): see bug 576033
 pref("network.http.accept-encoding", "gzip, deflate");
 
--- a/netwerk/base/ReferrerPolicy.h
+++ b/netwerk/base/ReferrerPolicy.h
@@ -20,17 +20,20 @@ enum ReferrerPolicy {
   /* spec tokens: default no-referrer-when-downgrade */
   RP_No_Referrer_When_Downgrade  = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
   RP_Default                     = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
 
   /* spec tokens: origin-when-cross-origin */
   RP_Origin_When_Crossorigin     = nsIHttpChannel::REFERRER_POLICY_ORIGIN_WHEN_XORIGIN,
 
   /* spec tokens: always unsafe-url */
-  RP_Unsafe_URL                  = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL
+  RP_Unsafe_URL                  = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL,
+
+  /* referrer policy is not set */
+  RP_Unset                       = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE
 };
 
 inline ReferrerPolicy
 ReferrerPolicyFromString(const nsAString& content)
 {
   // This is implemented step by step as described in the Referrer Policy
   // specification, section 6.4 "Determine token's Policy".
   if (content.LowerCaseEqualsLiteral("never") ||
--- a/parser/html/nsHtml5SpeculativeLoad.cpp
+++ b/parser/html/nsHtml5SpeculativeLoad.cpp
@@ -23,20 +23,20 @@ nsHtml5SpeculativeLoad::~nsHtml5Speculat
 void
 nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
 {
   switch (mOpCode) {
     case eSpeculativeLoadBase:
       aExecutor->SetSpeculationBase(mUrl);
       break;
     case eSpeculativeLoadMetaReferrer:
-      aExecutor->SetSpeculationReferrerPolicy(mMetaReferrerPolicy);
+      aExecutor->SetSpeculationReferrerPolicy(mReferrerPolicy);
       break;
     case eSpeculativeLoadImage:
-      aExecutor->PreloadImage(mUrl, mCrossOrigin, mSrcset, mSizes);
+      aExecutor->PreloadImage(mUrl, mCrossOrigin, mSrcset, mSizes, mReferrerPolicy);
       break;
     case eSpeculativeLoadOpenPicture:
       aExecutor->PreloadOpenPicture();
       break;
     case eSpeculativeLoadEndPicture:
       aExecutor->PreloadEndPicture();
       break;
     case eSpeculativeLoadPictureSource:
--- a/parser/html/nsHtml5SpeculativeLoad.h
+++ b/parser/html/nsHtml5SpeculativeLoad.h
@@ -40,30 +40,33 @@ class nsHtml5SpeculativeLoad {
       mOpCode = eSpeculativeLoadBase;
       mUrl.Assign(aUrl);
     }
 
     inline void InitMetaReferrerPolicy(const nsAString& aReferrerPolicy) {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = eSpeculativeLoadMetaReferrer;
-      mMetaReferrerPolicy.Assign(
+      mReferrerPolicy.Assign(
         nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aReferrerPolicy));
     }
 
     inline void InitImage(const nsAString& aUrl,
                           const nsAString& aCrossOrigin,
+                          const nsAString& aReferrerPolicy,
                           const nsAString& aSrcset,
                           const nsAString& aSizes)
     {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = eSpeculativeLoadImage;
       mUrl.Assign(aUrl);
       mCrossOrigin.Assign(aCrossOrigin);
+      mReferrerPolicy.Assign(
+        nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aReferrerPolicy));
       mSrcset.Assign(aSrcset);
       mSizes.Assign(aSizes);
     }
 
     // <picture> elements have multiple <source> nodes followed by an <img>,
     // where we use the first valid source, which may be the img. Because we
     // can't determine validity at this point without parsing CSS and getting
     // main thread state, we push preload operations for picture pushed and
@@ -174,17 +177,17 @@ class nsHtml5SpeculativeLoad {
       mCrossOrigin.Assign(aCrossOrigin);
     }
 
     void Perform(nsHtml5TreeOpExecutor* aExecutor);
 
   private:
     eHtml5SpeculativeLoad mOpCode;
     nsString mUrl;
-    nsString mMetaReferrerPolicy;
+    nsString mReferrerPolicy;
     /**
      * If mOpCode is eSpeculativeLoadStyle or eSpeculativeLoadScript[FromHead]
      * then this is the value of the "charset" attribute. For
      * eSpeculativeLoadSetDocumentCharset it is the charset that the
      * document's charset is being set to. Otherwise it's empty.
      */
     nsString mCharset;
     /**
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -120,21 +120,24 @@ nsHtml5TreeBuilder::createElement(int32_
     switch (aNamespace) {
       case kNameSpaceID_XHTML:
         if (nsHtml5Atoms::img == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
           nsString* srcset =
             aAttributes->getValue(nsHtml5AttributeName::ATTR_SRCSET);
           nsString* crossOrigin =
             aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+          nsString* referrerPolicy =
+            aAttributes->getValue(nsHtml5AttributeName::ATTR_REFERRER);
           nsString* sizes =
             aAttributes->getValue(nsHtml5AttributeName::ATTR_SIZES);
           mSpeculativeLoadQueue.AppendElement()->
             InitImage(url ? *url : NullString(),
                       crossOrigin ? *crossOrigin : NullString(),
+                      referrerPolicy ? *referrerPolicy : NullString(),
                       srcset ? *srcset : NullString(),
                       sizes ? *sizes : NullString());
         } else if (nsHtml5Atoms::source == aName) {
           nsString* srcset =
             aAttributes->getValue(nsHtml5AttributeName::ATTR_SRCSET);
           // Sources without srcset cannot be selected. The source could also be
           // for a media element, but in that context doesn't use srcset.  See
           // comments in nsHtml5SpeculativeLoad.h about <picture> preloading
@@ -198,16 +201,17 @@ nsHtml5TreeBuilder::createElement(int32_
               }
             }
           }
         } else if (nsHtml5Atoms::video == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
           if (url) {
             mSpeculativeLoadQueue.AppendElement()->InitImage(*url, NullString(),
                                                              NullString(),
+                                                             NullString(),
                                                              NullString());
           }
         } else if (nsHtml5Atoms::style == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
         } else if (nsHtml5Atoms::html == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
@@ -234,16 +238,17 @@ nsHtml5TreeBuilder::createElement(int32_
         }
         break;
       case kNameSpaceID_SVG:
         if (nsHtml5Atoms::image == aName) {
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             mSpeculativeLoadQueue.AppendElement()->InitImage(*url, NullString(),
                                                              NullString(),
+                                                             NullString(),
                                                              NullString());
           }
         } else if (nsHtml5Atoms::script == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -938,23 +938,35 @@ nsHtml5TreeOpExecutor::PreloadStyle(cons
   mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
                           mSpeculationReferrerPolicy);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
                                     const nsAString& aCrossOrigin,
                                     const nsAString& aSrcset,
-                                    const nsAString& aSizes)
+                                    const nsAString& aSizes,
+                                    const nsAString& aImageReferrerPolicy)
 {
   nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
   nsCOMPtr<nsIURI> uri = mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset,
                                                         aSizes);
   if (uri && ShouldPreloadURI(uri)) {
-    mDocument->MaybePreLoadImage(uri, aCrossOrigin, mSpeculationReferrerPolicy);
+    // use document wide referrer policy
+    mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
+    // if enabled in preferences, use the referrer attribute from the image, if provided
+    bool referrerAttributeEnabled = Preferences::GetBool("network.http.enablePerElementReferrer", false);
+    if (referrerAttributeEnabled) {
+      mozilla::net::ReferrerPolicy imageReferrerPolicy = mozilla::net::ReferrerPolicyFromString(aImageReferrerPolicy);
+      if (imageReferrerPolicy != mozilla::net::RP_Unset) {
+        referrerPolicy = imageReferrerPolicy;
+      }
+    }
+
+    mDocument->MaybePreLoadImage(uri, aCrossOrigin, referrerPolicy);
   }
 }
 
 // These calls inform the document of picture state and seen sources, such that
 // it can use them to inform ResolvePreLoadImage as necessary
 void
 nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
                                             const nsAString& aSizes,
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -251,17 +251,18 @@ class nsHtml5TreeOpExecutor final : publ
                        bool aScriptFromHead);
 
     void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
                       const nsAString& aCrossOrigin);
 
     void PreloadImage(const nsAString& aURL,
                       const nsAString& aCrossOrigin,
                       const nsAString& aSrcset,
-                      const nsAString& aSizes);
+                      const nsAString& aSizes,
+                      const nsAString& aImageReferrerPolicy);
 
     void PreloadOpenPicture();
 
     void PreloadEndPicture();
 
     void PreloadPictureSource(const nsAString& aSrcset,
                               const nsAString& aSizes,
                               const nsAString& aType,