Bug 1261298 - W3C referrer policy attribute is not passed to image. r?jdm draft
authorThomas Nguyen <tnguyen@mozilla.com>
Tue, 10 May 2016 14:28:01 +0800
changeset 365181 bab3e95b93cc117b379560a735fb0cffca4d7315
parent 364682 bae525a694e2dc0aa433885be8751330d4995a49
child 520474 19178a7868c51041cd731f7094f95f7092d5b1b6
push id17656
push usertnguyen@mozilla.com
push dateTue, 10 May 2016 06:28:51 +0000
reviewersjdm
bugs1261298
milestone49.0a1
Bug 1261298 - W3C referrer policy attribute is not passed to image. r?jdm MozReview-Commit-ID: 4Z0CpywpSDJ
dom/base/test/img_referrer_testserver.sjs
dom/base/test/test_img_referrer.html
dom/html/HTMLImageElement.cpp
--- a/dom/base/test/img_referrer_testserver.sjs
+++ b/dom/base/test/img_referrer_testserver.sjs
@@ -1,15 +1,20 @@
 var BASE_URL = 'example.com/tests/dom/base/test/img_referrer_testserver.sjs';
+const IMG_BYTES = atob(
+  "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+  "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
 
-function createTestUrl(aPolicy, aAction, aName) {
+function createTestUrl(aPolicy, aAction, aName, aContent) {
+  var content = aContent || 'text';
   return 'http://' + BASE_URL + '?' +
          'action=' + aAction + '&' +
          'policy=' + aPolicy + '&' +
-         'name=' + aName;
+         'name=' + aName + '&' +
+         'content=' + content;
 }
 
 function createTestPage(aHead, aImgPolicy, aName) {
   var _createTestUrl = createTestUrl.bind(null, aImgPolicy, 'test', aName);
 
   return '<!DOCTYPE HTML>\n\
          <html>'+
             aHead +
@@ -83,16 +88,52 @@ function createTestPage2(aHead, aPolicy,
                  parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
                }.bind(window), false);' +
 
              '</script>\n\
            </body>\n\
          </html>';
 }
 
+function createTestPage3(aPolicy, aName) {
+  return '<!DOCTYPE HTML>\n\
+         <html>\n\
+           <body>\n\
+             <script>' +
+               'var image = new Image();\n\
+               image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+               image.referrerPolicy = "' + aPolicy + '";\n\
+               image.onload = function() {\n\
+                 window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+               }\n\
+               document.body.appendChild(image);' +
+
+             '</script>\n\
+           </body>\n\
+         </html>';
+}
+
+function createTestPage4(aPolicy, aName) {
+  return '<!DOCTYPE HTML>\n\
+         <html>\n\
+           <body>\n\
+             <script>' +
+               'var image = new Image();\n\
+               image.referrerPolicy = "' + aPolicy + '";\n\
+               image.src = "' + createTestUrl(aPolicy, "test", aName, "image") + '";\n\
+               image.onload = function() {\n\
+                 window.parent.postMessage("childLoadComplete", "http://mochi.test:8888");\n\
+               }\n\
+               document.body.appendChild(image);' +
+
+             '</script>\n\
+           </body>\n\
+         </html>';
+}
+
 function createTest4(aPolicy, aName) {
   var headString = '<head>';
   headString += '<meta name="referrer" content="' + aPolicy + '">';
   headString += '<script></script>';
 
   return createTestPage2(headString, aPolicy, aName);
 }
 
@@ -114,19 +155,20 @@ function handleRequest(request, response
   if (action === 'resetState') {
     var state = getSharedState(sharedKey);
     state = {};
     setSharedState(sharedKey, JSON.stringify(state));
     response.write("");
     return;
   }
   if (action === 'test') {
-    // ?action=test&policy=origin&name=name
+    // ?action=test&policy=origin&name=name&content=content
     var policy = params[1].split('=')[1];
     var name = params[2].split('=')[1];
+    var content = params[3].split('=')[1];
     var result = getSharedState(sharedKey);
 
     if (result === '') {
       result = {};
     } else {
       result = JSON.parse(result);
     }
 
@@ -148,16 +190,21 @@ function handleRequest(request, response
       test.referrer = '';
     }
     test.policy = referrerLevel;
     test.expected = policy;
 
     result["tests"][name] = test;
 
     setSharedState(sharedKey, JSON.stringify(result));
+
+    if (content === 'img' || content === 'image') {
+      response.setHeader("Content-Type", "image/png");
+      response.write(IMG_BYTES);
+    }
     return;
   }
   if (action === 'get-test-results') {
     // ?action=get-result
     response.write(getSharedState(sharedKey));
     return;
   }
   if (action === 'generate-img-policy-test') {
@@ -202,11 +249,29 @@ function handleRequest(request, response
     // ?action=generate-img-policy-test5&policy=b64-encoded-string&name=name
     var policy = unescape(params[1].split('=')[1]);
     var name = unescape(params[2].split('=')[1]);
 
     response.write(createTest5(policy, name));
     return;
   }
 
+  if (action === 'generate-setAttribute-test1') {
+    // ?action=generate-setAttribute-test1&policy=b64-encoded-string&name=name
+    var policy = unescape(params[1].split('=')[1]);
+    var name = unescape(params[2].split('=')[1]);
+
+    response.write(createTestPage3(policy, name));
+    return;
+  }
+
+  if (action === 'generate-setAttribute-test2') {
+    // ?action=generate-setAttribute-test2&policy=b64-encoded-string&name=name
+    var policy = unescape(params[1].split('=')[1]);
+    var name = unescape(params[2].split('=')[1]);
+
+    response.write(createTestPage4(policy, name));
+    return;
+  }
+
   response.write("I don't know action "+action);
   return;
 }
--- a/dom/base/test/test_img_referrer.html
+++ b/dom/base/test/test_img_referrer.html
@@ -16,29 +16,23 @@ Testing that img referrer attribute is h
 https://bugzilla.mozilla.org/show_bug.cgi?id=1166910
 -->
 
 <script type="application/javascript;version=1.7">
 
 SimpleTest.waitForExplicitFinish();
 var advance = function() { tests.next(); };
 
-var mTestResult;
-
 /**
  * Listen for notifications from the child.
  * These are sent in case of error, or when the loads we await have completed.
  */
 window.addEventListener("message", function(event) {
-  if (event.data == "childLoadComplete") {
-    // all loads happen, continue the test.
-    advance();
-  }
-  else if (event.data.contains("childLoadComplete")) {
-    mTestResult = event.data.split(",")[1];
+  if (event.data == "childLoadComplete" ||
+      event.data.contains("childLoadComplete")) {
     advance();
   }
 });
 
 /**
  * helper to perform an XHR.
  */
 function doXHR(aUrl, onSuccess, onFail) {
@@ -58,24 +52,21 @@ function doXHR(aUrl, onSuccess, onFail) 
  * Grabs the results via XHR and passes to checker.
  */
 function checkIndividualResults(aTestname, aExpectedImg, aName) {
   doXHR('/tests/dom/base/test/img_referrer_testserver.sjs?action=get-test-results',
         function(xhr) {
           var results = xhr.response;
           info(JSON.stringify(xhr.response));
 
-          if (aName === 'setAttribute') {
-            is(mTestResult, aExpectedImg, aTestname + ' --- ' + mTestResult);
-          } else {
-            for (i in aName) {
-              ok(aName[i] in results.tests, aName[i] + " tests have to be performed.");
-              is(results.tests[aName[i]].policy, aExpectedImg[i], aTestname + ' --- ' + results.tests[aName[i]].policy + ' (' + results.tests[aName[i]].referrer + ')');
-            }
+          for (i in aName) {
+            ok(aName[i] in results.tests, aName[i] + " tests have to be performed.");
+            is(results.tests[aName[i]].policy, aExpectedImg[i], aTestname + ' --- ' + results.tests[aName[i]].policy + ' (' + results.tests[aName[i]].referrer + ')');
           }
+
           advance();
         },
         function(xhr) {
           ok(false, "Can't get results from the counter server.");
           SimpleTest.finish();
         });
 }
 
@@ -151,16 +142,29 @@ var tests = (function() {
   yield checkIndividualResults("no-referrer in meta (no img referrer policy), speculative load", ["none"], [name]);
 
   yield resetState();
   sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-img-policy-test5";
   name = 'regular-load-no-referrer-meta';
   yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
   yield checkIndividualResults("no-referrer in meta (no img referrer policy), regular load", ["none"], [name]);
 
+  //test setAttribute
+  yield resetState();
+  sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test1";
+  name = 'set-referrer-policy-attribute-before-src';
+  yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+  yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
+  yield resetState();
+  sjs = "/tests/dom/base/test/img_referrer_testserver.sjs?action=generate-setAttribute-test2";
+  name = 'set-referrer-policy-attribute-after-src';
+  yield iframe.src = sjs + "&policy=" + escape('no-referrer') + "&name=" + name;
+  yield checkIndividualResults("no-referrer in img", ["none"], [name]);
+
   // complete.  Be sure to yield so we don't call this twice.
   yield SimpleTest.finish();
 })();
 
 </script>
 </head>
 
 <body onload="tests.next();">
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -41,20 +41,24 @@
 
 #include "nsILoadGroup.h"
 
 #include "nsRuleData.h"
 
 #include "nsIDOMHTMLMapElement.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/net/ReferrerPolicy.h"
 
 #include "nsLayoutUtils.h"
 
 #include "mozilla/Preferences.h"
+
+using namespace mozilla::net;
+
 static const char *kPrefSrcsetEnabled = "dom.image.srcset.enabled";
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
 
 #ifdef DEBUG
 // Is aSubject a previous sibling of aNode.
 static bool IsPreviousSibling(nsINode *aSubject, nsINode *aNode)
 {
@@ -507,17 +511,17 @@ HTMLImageElement::IsHTMLFocusable(bool a
   return false;
 }
 
 nsresult
 HTMLImageElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                           nsIAtom* aPrefix, const nsAString& aValue,
                           bool aNotify)
 {
-  bool forceReloadWithNewCORSMode = false;
+  bool forceReload = false;
   // We need to force our image to reload.  This must be done here, not in
   // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
   // being set to its existing value, which is normally optimized away as a
   // no-op.
   //
   // If we are in responsive mode, we drop the forced reload behavior,
   // but still trigger a image load task for img.src = img.src per
   // spec.
@@ -553,27 +557,39 @@ HTMLImageElement::SetAttr(int32_t aNameS
     }
   } else if (aNameSpaceID == kNameSpaceID_None &&
              aName == nsGkAtoms::crossorigin &&
              aNotify) {
     nsAttrValue attrValue;
     ParseCORSValue(aValue, attrValue);
     if (GetCORSMode() != AttrValueToCORSMode(&attrValue)) {
       // Force a new load of the image with the new cross origin policy.
-      forceReloadWithNewCORSMode = true;
+      forceReload = true;
+    }
+  } else if (aName == nsGkAtoms::referrerpolicy &&
+      aNameSpaceID == kNameSpaceID_None &&
+      aNotify) {
+    ReferrerPolicy referrerPolicy = ReferrerPolicyFromString(aValue);
+    if (!InResponsiveMode() && referrerPolicy != GetImageReferrerPolicy()) {
+      // XXX: Bug 1076583 - We still use the older synchronous algorithm
+      // Because referrerPolicy is not treated as relevant mutations, setting
+      // the attribute will neither trigger a reload nor update the referrer
+      // policy of the loading channel (whether it has previously completed or
+      // not). Force a new load of the image with the new referrerpolicy.
+      forceReload = true;
     }
   }
 
   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
                                               aValue, aNotify);
 
   // Because we load image synchronously in non-responsive-mode, we need to do
   // reload after the attribute has been set if the reload is triggerred by
   // cross origin changing.
-  if (forceReloadWithNewCORSMode) {
+  if (forceReload) {
     if (InResponsiveMode()) {
       // per spec, full selection runs when this changes, even though
       // it doesn't directly affect the source selection
       QueueImageLoadTask(true);
     } else {
       // Bug 1076583 - We still use the older synchronous algorithm in
       // non-responsive mode. Force a new load of the image with the
       // new cross origin policy