Commit d8dfdb9b authored by estark@chromium.org's avatar estark@chromium.org

Implement referrerpolicy attribute for img elements

The 'referrerpolicy' attribute controls the referrer for the outgoing
request when an image is loaded.

BUG=490608

Review URL: https://codereview.chromium.org/1291613010

git-svn-id: svn://svn.chromium.org/blink/trunk@200940 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent fb4895d7
<!DOCTYPE html>
<html>
<body>
<meta name="referrer" content="origin">
<img src="resources/green250x50.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green250x50.png" />
</body>
</html
<!DOCTYPE html>
<html>
<head>
<script src="/resources/get-host-info.js"></script>
</head>
<body>
<script>
if (window.testRunner)
testRunner.waitUntilDone();
if (window.location.origin != get_host_info().HTTPS_ORIGIN) {
window.location = get_host_info().HTTPS_ORIGIN + window.location.pathname;
} else {
var img = document.createElement("img");
img.src = "http://127.0.0.1:8000/security/resources/green-if-no-referrer.php";
img.referrerpolicy = "no-referrer-when-downgrade";
document.body.appendChild(img);
testRunner.notifyDone();
}
</script>
</body>
</html
<!DOCTYPE html>
<html>
<body>
<meta name="referrer" content="origin">
<img src="resources/green-if-no-referrer.php"
referrerpolicy="no-referrer" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green250x50.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green250x50.png" />
<img src="resources/green250x50.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="http://localhost:8000/security/resources/green-if-referrer-is-origin.php"
referrerpolicy="origin-when-crossorigin" />
<img src="resources/green-if-referrer-is-full-url-origin-when-crossorigin.php"
referrerpolicy="origin-when-crossorigin" />
</body>
</html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green-if-referrer-is-origin.php"
referrerpolicy="origin" />
</body>
</html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green250x50.png" />
<img src="resources/green250x50.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<picture>
<source srcset="resources/abe.png" media="(false)">
<img src="resources/green-if-no-referrer.php"
referrerpolicy="no-referrer" />
</picture>
<picture>
<source srcset="resources/green-if-no-referrer.php" type="image/png">
<img src="resources/abe.png"
referrerpolicy="no-referrer" />
</picture>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/red200x100.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="http://127.0.0.1:8000/security/resources/green-if-referrer-is-origin.php"
referrerpolicy="origin" id="image" />
<script>
// Remove the image's referrerpolicy attribute and set a new
// src to send a new request.
var img = document.getElementById("image");
img.removeAttribute("referrerpolicy");
img.src = "http://127.0.0.1:8000/security/resources/green-if-no-referrer.php";
// The expected result is a red square because a referrer will be
// sent according to the default referrerpolicy.
</script>
</body>
</html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="resources/green250x50.png" />
</body>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="http://localhost:8000/security/resources/green-if-referrer-is-full-url.php"
referrerpolicy="unsafe-url" />
</body>
</html
......@@ -9,9 +9,15 @@
var anch = document.createElement("a");
anch.href = "http://localhost:8000/security/resources/referrer-attr-anchor-target.html#no-policy";
document.body.appendChild(anch);
var img = document.createElement("img");
img.src = "http://localhost:8000/security/resources/compass.jpg";
document.body.appendChild(img);
test(function () {
assert_equals(anch.referrerpolicy, "");
}, "Dynamically created anchor has empty referrerpolicy");
assert_equals(img.referrerpolicy, "");
}, "Dynamically created elements have empty referrerpolicy");
anch.click();
</script>
</body>
......
<?php
header('Content-Type: image/png');
if ($_SERVER['HTTP_REFERER'] != 'http://127.0.0.1:8000/security/referrer-policy-attribute-img-origin-when-crossorigin.html') {
$img = 'red200x100.png';
} else {
$img = 'green250x50.png';
}
echo file_get_contents($img);
?>
\ No newline at end of file
<?php
header('Content-Type: image/png');
if ($_SERVER['HTTP_REFERER'] != 'http://127.0.0.1:8000/security/referrer-policy-attribute-img-unsafe-url.html') {
$img = 'red200x100.png';
} else {
$img = 'green250x50.png';
}
echo file_get_contents($img);
?>
\ No newline at end of file
<?php
header('Content-Type: image/png');
if ($_SERVER['HTTP_REFERER'] != 'http://127.0.0.1:8000/') {
$img = 'red200x100.png';
} else {
$img = 'green250x50.png';
}
echo file_get_contents($img);
?>
......@@ -16,7 +16,7 @@
else if (hash === "unsafe-url")
expected_referrer = "http://127.0.0.1:8000/security/referrer-policy-attribute-anchor-unsafe-url.html";
else if (hash === "no-policy")
expected_referrer = "http://127.0.0.1:8000/security/referrer-policy-attribute-anchor-no-policy.html";
expected_referrer = "http://127.0.0.1:8000/security/referrer-policy-attribute-no-policy.html";
else if (hash === "origin-when-crossorigin")
expected_referrer = "http://127.0.0.1:8000/";
else if (hash === "no-referrer" ||
......
......@@ -572,6 +572,7 @@ html element img
property name
property naturalHeight
property naturalWidth
property referrerpolicy
property sizes
property src
property srcset
......
......@@ -2108,6 +2108,7 @@ interface HTMLImageElement
getter name
getter naturalHeight
getter naturalWidth
getter referrerpolicy
getter sizes
getter src
getter srcset
......@@ -2127,6 +2128,7 @@ interface HTMLImageElement
setter longDesc
setter lowsrc
setter name
setter referrerpolicy
setter sizes
setter src
setter srcset
......
......@@ -51,6 +51,7 @@
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/MIMETypeRegistry.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/weborigin/SecurityPolicy.h"
namespace blink {
......@@ -91,6 +92,7 @@ HTMLImageElement::HTMLImageElement(Document& document, HTMLFormElement* form, bo
, m_intrinsicSizingViewportDependant(false)
, m_useFallbackContent(false)
, m_isFallbackImage(false)
, m_referrerPolicy(ReferrerPolicyDefault)
{
setHasCustomStyleCallbacks();
if (form && form->inDocument()) {
......@@ -268,6 +270,10 @@ void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicStr
selectSourceURL(ImageLoader::UpdateIgnorePreviousError);
} else if (name == usemapAttr) {
setIsLink(!value.isNull());
} else if (name == referrerpolicyAttr) {
m_referrerPolicy = ReferrerPolicyDefault;
if (!value.isNull())
SecurityPolicy::referrerPolicyFromString(value, &m_referrerPolicy);
} else {
HTMLElement::parseAttribute(name, value);
}
......@@ -389,7 +395,7 @@ Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode*
// If we have been inserted from a layoutObject-less document,
// our loader may have not fetched the image, so do it now.
if ((insertionPoint->inDocument() && !imageLoader().image()) || imageWasModified)
imageLoader().updateFromElement(ImageLoader::UpdateNormal);
imageLoader().updateFromElement(ImageLoader::UpdateNormal, m_referrerPolicy);
return HTMLElement::insertedInto(insertionPoint);
}
......@@ -660,7 +666,7 @@ float HTMLImageElement::sourceSize(Element& element)
void HTMLImageElement::forceReload() const
{
imageLoader().updateFromElement(ImageLoader::UpdateForcedReload);
imageLoader().updateFromElement(ImageLoader::UpdateForcedReload, m_referrerPolicy);
}
void HTMLImageElement::selectSourceURL(ImageLoader::UpdateFromElementBehavior behavior)
......@@ -683,7 +689,7 @@ void HTMLImageElement::selectSourceURL(ImageLoader::UpdateFromElementBehavior be
m_listener = ViewportChangeListener::create(this);
document().mediaQueryMatcher().addViewportListener(m_listener);
}
imageLoader().updateFromElement(behavior);
imageLoader().updateFromElement(behavior, m_referrerPolicy);
if (imageLoader().image() || (imageLoader().hasPendingActivity() && !imageSourceURL().isEmpty()))
ensurePrimaryContent();
......
......@@ -170,6 +170,8 @@ private:
unsigned m_intrinsicSizingViewportDependant : 1;
unsigned m_useFallbackContent : 1;
unsigned m_isFallbackImage : 1;
ReferrerPolicy m_referrerPolicy;
};
} // namespace blink
......
......@@ -38,6 +38,7 @@
readonly attribute long naturalHeight;
readonly attribute boolean complete;
readonly attribute DOMString currentSrc;
[RuntimeEnabled=ReferrerPolicyAttribute, Reflect, ReflectOnly=("","no-referrer","origin","no-referrer-when-downgrade","origin-when-cross-origin","unsafe-url"), ReflectMissing="", ReflectInvalid=""] attribute DOMString referrerpolicy;
// obsolete members
// https://html.spec.whatwg.org/#HTMLImageElement-partial
......
......@@ -123,6 +123,8 @@ public:
, m_defer(FetchRequest::NoDefer)
, m_allowCredentials(DoNotAllowStoredCredentials)
, m_mediaValues(mediaValues)
, m_referrerPolicySet(false)
, m_referrerPolicy(ReferrerPolicyDefault)
{
ASSERT(m_mediaValues->isCached());
if (match(m_tagImpl, imgTag)
......@@ -174,7 +176,7 @@ public:
}
}
PassOwnPtr<PreloadRequest> createPreloadRequest(const KURL& predictedBaseURL, const SegmentedString& source, const ClientHintsPreferences& clientHintsPreferences, const PictureData& pictureData, const ReferrerPolicy referrerPolicy)
PassOwnPtr<PreloadRequest> createPreloadRequest(const KURL& predictedBaseURL, const SegmentedString& source, const ClientHintsPreferences& clientHintsPreferences, const PictureData& pictureData, const ReferrerPolicy documentReferrerPolicy)
{
PreloadRequest::RequestType requestType = PreloadRequest::RequestTypePreload;
if (shouldPreconnect())
......@@ -195,6 +197,8 @@ public:
resourceWidth.isSet = true;
}
// The element's 'referrerpolicy' attribute (if present) takes precedence over the document's referrer policy.
ReferrerPolicy referrerPolicy = m_referrerPolicy != ReferrerPolicyDefault ? m_referrerPolicy : documentReferrerPolicy;
OwnPtr<PreloadRequest> request = PreloadRequest::create(initiatorFor(m_tagImpl), position, m_urlToLoad, predictedBaseURL, resourceType(), referrerPolicy, resourceWidth, clientHintsPreferences, requestType);
if (isCORSEnabled())
request->setCrossOriginEnabled(allowStoredCredentials());
......@@ -237,6 +241,9 @@ private:
m_srcsetImageCandidate = bestFitSourceForSrcsetAttribute(m_mediaValues->devicePixelRatio(), m_sourceSize, m_srcsetAttributeValue);
setUrlToLoad(bestFitSourceForImageAttributes(m_mediaValues->devicePixelRatio(), m_sourceSize, m_imgSrcUrl, m_srcsetImageCandidate), AllowURLReplacement);
}
} else if (!m_referrerPolicySet && match(attributeName, referrerpolicyAttr) && !attributeValue.isNull()) {
m_referrerPolicySet = true;
SecurityPolicy::referrerPolicyFromString(attributeValue, &m_referrerPolicy);
}
}
......@@ -411,6 +418,8 @@ private:
FetchRequest::DeferOption m_defer;
StoredCredentials m_allowCredentials;
RefPtrWillBeMember<MediaValues> m_mediaValues;
bool m_referrerPolicySet;
ReferrerPolicy m_referrerPolicy;
};
TokenPreloadScanner::TokenPreloadScanner(const KURL& documentURL, PassOwnPtr<CachedDocumentParameters> documentParameters)
......
......@@ -34,6 +34,16 @@ struct PreconnectTestCase {
CrossOriginAttributeValue crossOrigin;
};
struct ReferrerPolicyTestCase {
const char* baseURL;
const char* inputHTML;
const char* preloadedURL; // Or nullptr if no preload is expected.
const char* outputBaseURL;
Resource::Type type;
int resourceWidth;
ReferrerPolicy referrerPolicy;
};
class MockHTMLResourcePreloader : public ResourcePreloader {
public:
void preloadRequestVerification(Resource::Type type, const char* url, const char* baseURL, int width, const ClientHintsPreferences& preferences)
......@@ -52,6 +62,12 @@ public:
EXPECT_EQ(preferences.shouldSendViewportWidth(), m_preloadRequest->preferences().shouldSendViewportWidth());
}
void preloadRequestVerification(Resource::Type type, const char* url, const char* baseURL, int width, ReferrerPolicy referrerPolicy)
{
preloadRequestVerification(type, url, baseURL, width, ClientHintsPreferences());
EXPECT_EQ(referrerPolicy, m_preloadRequest->referrerPolicy());
}
void preconnectRequestVerification(const String& host, CrossOriginAttributeValue crossOrigin)
{
if (!host.isNull()) {
......@@ -142,6 +158,16 @@ protected:
preloader.preconnectRequestVerification(testCase.preconnectedHost, testCase.crossOrigin);
}
void test(ReferrerPolicyTestCase testCase)
{
MockHTMLResourcePreloader preloader;
KURL baseURL(ParsedURLString, testCase.baseURL);
m_scanner->appendToEnd(String(testCase.inputHTML));
m_scanner->scan(&preloader, baseURL);
preloader.preloadRequestVerification(testCase.type, testCase.preloadedURL, testCase.outputBaseURL, testCase.resourceWidth, testCase.referrerPolicy);
}
private:
OwnPtr<DummyPageHolder> m_dummyPageHolder;
OwnPtr<HTMLPreloadScanner> m_scanner;
......@@ -297,4 +323,22 @@ TEST_F(HTMLPreloadScannerTest, testPicture)
test(testCase);
}
TEST_F(HTMLPreloadScannerTest, testReferrerPolicy)
{
ReferrerPolicyTestCase testCases[] = {
{ "http://example.test", "<img src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyDefault },
{ "http://example.test", "<img referrerpolicy='origin' src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyOrigin },
{ "http://example.test", "<meta name='referrer' content='not-a-valid-policy'><img src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyDefault },
{ "http://example.test", "<img referrerpolicy='origin' referrerpolicy='origin-when-crossorigin' src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyOrigin },
{ "http://example.test", "<img referrerpolicy='not-a-valid-policy' src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyDefault },
{ "http://example.test", "<meta name='referrer' content='no-referrer'><img referrerpolicy='origin' src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyOrigin },
// The scanner's state is not reset between test cases, so all subsequent test cases have a document referrer policy of no-referrer.
{ "http://example.test", "<img referrerpolicy='not-a-valid-policy' src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyNever },
{ "http://example.test", "<img src='bla.gif'/>", "bla.gif", "http://example.test/", Resource::Image, 0, ReferrerPolicyNever }
};
for (const auto& testCase : testCases)
test(testCase);
}
} // namespace blink
......@@ -47,6 +47,7 @@ public:
bool isCORS() const { return m_isCORSEnabled; }
bool isAllowCredentials() const { return m_allowCredentials == AllowStoredCredentials; }
const ClientHintsPreferences& preferences() const { return m_clientHintsPreferences; }
ReferrerPolicy referrerPolicy() const { return m_referrerPolicy; }
private:
PreloadRequest(const String& initiatorName,
......
......@@ -46,6 +46,7 @@
#include "core/svg/graphics/SVGImage.h"
#include "platform/Logging.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/weborigin/SecurityPolicy.h"
#include "public/platform/WebURLRequest.h"
namespace blink {
......@@ -78,16 +79,17 @@ static ImageLoader::BypassMainWorldBehavior shouldBypassMainWorldCSP(ImageLoader
class ImageLoader::Task : public WebThread::Task {
public:
static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
static PassOwnPtr<Task> create(ImageLoader* loader, UpdateFromElementBehavior updateBehavior, ReferrerPolicy referrerPolicy)
{
return adoptPtr(new Task(loader, updateBehavior));
return adoptPtr(new Task(loader, updateBehavior, referrerPolicy));
}
Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior)
Task(ImageLoader* loader, UpdateFromElementBehavior updateBehavior, ReferrerPolicy referrerPolicy)
: m_loader(loader)
, m_shouldBypassMainWorldCSP(shouldBypassMainWorldCSP(loader))
, m_updateBehavior(updateBehavior)
, m_weakFactory(this)
, m_referrerPolicy(referrerPolicy)
{
v8::Isolate* isolate = V8PerIsolateData::mainThreadIsolate();
v8::HandleScope scope(isolate);
......@@ -109,9 +111,9 @@ public:
return;
if (m_scriptState->contextIsValid()) {
ScriptState::Scope scope(m_scriptState.get());
m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior);
m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior, m_referrerPolicy);
} else {
m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior);
m_loader->doUpdateFromElement(m_shouldBypassMainWorldCSP, m_updateBehavior, m_referrerPolicy);
}
}
......@@ -132,6 +134,7 @@ private:
UpdateFromElementBehavior m_updateBehavior;
RefPtr<ScriptState> m_scriptState;
WeakPtrFactory<Task> m_weakFactory;
ReferrerPolicy m_referrerPolicy;
};
ImageLoader::ImageLoader(Element* element)
......@@ -273,15 +276,15 @@ inline void ImageLoader::clearFailedLoadURL()
m_failedLoadURL = AtomicString();
}
inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior updateBehavior)
inline void ImageLoader::enqueueImageLoadingMicroTask(UpdateFromElementBehavior updateBehavior, ReferrerPolicy referrerPolicy)
{
OwnPtr<Task> task = Task::create(this, updateBehavior);
OwnPtr<Task> task = Task::create(this, updateBehavior, referrerPolicy);
m_pendingTask = task->createWeakPtr();
Microtask::enqueueMicrotask(task.release());
m_loadDelayCounter = IncrementLoadEventDelayCount::create(m_element->document());
}
void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior)
void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, UpdateFromElementBehavior updateBehavior, ReferrerPolicy referrerPolicy)
{
// FIXME: According to
// http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-img-element:the-img-element-55
......@@ -314,6 +317,10 @@ void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, Up
// ImageLoader defers the load of images when in an ImageDocument. Don't defer this load on a forced reload.
m_loadingImageDocument = false;
}
if (referrerPolicy != ReferrerPolicyDefault)
resourceRequest.setHTTPReferrer(SecurityPolicy::generateReferrer(referrerPolicy, url, document.outgoingReferrer()));
if (isHTMLPictureElement(element()->parentNode()) || !element()->fastGetAttribute(HTMLNames::srcsetAttr).isNull())
resourceRequest.setRequestContext(WebURLRequest::RequestContextImageSet);
FetchRequest request(resourceRequest, element()->localName(), resourceLoaderOptions);
......@@ -389,7 +396,7 @@ void ImageLoader::doUpdateFromElement(BypassMainWorldBehavior bypassBehavior, Up
updatedHasPendingEvent();
}
void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior)
void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior, ReferrerPolicy referrerPolicy)
{
AtomicString imageSourceURL = m_element->imageSourceURL();
m_suppressErrorEvents = (updateBehavior == UpdateSizeChanged);
......@@ -409,7 +416,7 @@ void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior)
KURL url = imageSourceToKURL(imageSourceURL);
if (shouldLoadImmediately(url)) {
doUpdateFromElement(DoNotBypassMainWorldCSP, updateBehavior);
doUpdateFromElement(DoNotBypassMainWorldCSP, updateBehavior, referrerPolicy);
return;
}
// Allow the idiom "img.src=''; img.src='.." to clear down the image before
......@@ -425,7 +432,7 @@ void ImageLoader::updateFromElement(UpdateFromElementBehavior updateBehavior)
// raw HTML parsing case by loading images we don't intend to display.
Document& document = m_element->document();
if (document.isActive())
enqueueImageLoadingMicroTask(updateBehavior);
enqueueImageLoadingMicroTask(updateBehavior, referrerPolicy);
}
KURL ImageLoader::imageSourceToKURL(AtomicString imageSourceURL) const
......
......@@ -85,7 +85,7 @@ public:
DoNotBypassMainWorldCSP
};
void updateFromElement(UpdateFromElementBehavior = UpdateNormal);
void updateFromElement(UpdateFromElementBehavior = UpdateNormal, ReferrerPolicy = ReferrerPolicyDefault);
void elementDidMoveToNewDocument();
......@@ -121,7 +121,7 @@ private:
class Task;
// Called from the task or from updateFromElement to initiate the load.
void doUpdateFromElement(BypassMainWorldBehavior, UpdateFromElementBehavior);
void doUpdateFromElement(BypassMainWorldBehavior, UpdateFromElementBehavior, ReferrerPolicy = ReferrerPolicyDefault);
virtual void dispatchLoadEvent() = 0;
virtual void noImageResourceToLoad() { }
......@@ -139,7 +139,7 @@ private:
void clearFailedLoadURL();
void dispatchErrorEvent();
void crossSiteOrCSPViolationOccurred(AtomicString);
void enqueueImageLoadingMicroTask(UpdateFromElementBehavior);
void enqueueImageLoadingMicroTask(UpdateFromElementBehavior, ReferrerPolicy);
void timerFired(Timer<ImageLoader>*);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment