<!DOCTYPE HTML>
<html>
<!--
Tests for Mixed Content Blocker
https://bugzilla.mozilla.org/show_bug.cgi?id=62178
-->
<head>
  <meta charset="utf-8">
  <title>Tests for Bug 62178</title>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="testContent"></div>

<!-- types the Mixed Content Blocker can block
     /*
  switch (aContentType) {
  case nsIContentPolicy::TYPE_OBJECT:
  case nsIContentPolicy::TYPE_SCRIPT:
  case nsIContentPolicy::TYPE_STYLESHEET:
  case nsIContentPolicy::TYPE_SUBDOCUMENT:
  case nsIContentPolicy::TYPE_XMLHTTPREQUEST:

  case nsIContentPolicy::TYPE_FONT: - NO TEST:
    Load events for external fonts are not detectable by javascript.
  case nsIContentPolicy::TYPE_WEBSOCKET: - NO TEST:
    websocket connections over https require an encrypted websocket protocol (wss:)

  case nsIContentPolicy::TYPE_IMAGE:
  case nsIContentPolicy::TYPE_IMAGESET:
  case nsIContentPolicy::TYPE_MEDIA:
  case nsIContentPolicy::TYPE_PING:
    our ping implementation is off by default and does not comply with the current spec (bug 786347)
  case nsIContentPolicy::TYPE_BEACON:

  }
     */
-->

<script>
  function ok(a, msg) {
    parent.postMessage({msg, check: true, status: !!a}, "http://mochi.test:8888");
  }

  function is(a, b, msg) {
    ok(a == b, msg);
  }

  function report(type, msg) {
    parent.postMessage({test: type, msg}, "http://mochi.test:8888");
  }

  function uniqueID() {
    return Math.floor(Math.random() * 100000)
  }
  function uniqueIDParam(id) {
    return "&uniqueID=" + id;
  }

  async function init() {
    var baseUrl = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs";
    var checkLastRequestUrl = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs?lastRequest=true";

    //For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds.
    var MAX_COUNT = 100;
    var TIMEOUT_INTERVAL = 100;

    var testContent = document.getElementById("testContent");

    async function checkLastRequest() {
      const response = await fetch(checkLastRequestUrl);
      return response.json();
    }

    /* Part 1: Mixed Script tests */

    // Test 1a: insecure object
    var object = document.createElement("object");
    var objectId = uniqueID();
    object.data = baseUrl + "?type=object" + uniqueIDParam(objectId);
    object.type = "image/png";
    object.width = "200";
    object.height = "200";

    testContent.appendChild(object);

    var objectCount = 0;

    function objectStatus(object) {
      // Expose our privileged bits on the object.  We will match the MIME type to the one
      // provided by file_server.sjs
      object = SpecialPowers.wrap(object);
      var typeIsSet = object.actualType && (object.actualType !== '');
      var isLoaded = typeIsSet && object.actualType === 'application/x-test-match';
      if (isLoaded) {
        //object loaded
        report("object", "insecure object loaded");
      }
      else {
        if(!typeIsSet && objectCount < MAX_COUNT) {
          objectCount++;
          setTimeout(objectStatus, TIMEOUT_INTERVAL, object);
        }
        else {
          //After we have called setTimeout the maximum number of times, assume object is blocked
          report("object", "insecure object blocked");
        }
      }
    }

    // object does not have onload and onerror events. Hence we need a setTimeout to check the object's status
    setTimeout(objectStatus, TIMEOUT_INTERVAL, object);

    // Test 1b: insecure script
    var script = document.createElement("script");
    var scriptId = uniqueID();
    var scriptLoad = false;
    var scriptCount = 0;
    script.src = baseUrl + "?type=script" + uniqueIDParam(scriptId);
    script.onload = function(e) {
      report("script", "insecure script loaded");
      scriptLoad = true;
    }
    testContent.appendChild(script);

    function scriptStatus(script)
    {
      if(scriptLoad) {
        return;
      }
      else {
        if(scriptCount < MAX_COUNT) {
          scriptCount++;
          setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);
        }
        else {
          //After we have called setTimeout the maximum number of times, assume script is blocked
          report("script", "insecure script blocked");
        }
      }
    }

    // scripts blocked by Content Policy's do not have onerror events (see bug 789856).  Hence we need a setTimeout to check the script's status
    setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);


    // Test 1c: insecure stylesheet
    var cssStyleSheet = document.createElement("link");
    var cssStyleSheetId = uniqueID();
    cssStyleSheet.rel = "stylesheet";
    cssStyleSheet.href = baseUrl + "?type=stylesheet" + uniqueIDParam(cssStyleSheetId);
    cssStyleSheet.type = "text/css";
    testContent.appendChild(cssStyleSheet);

    var styleCount = 0;

    function styleStatus(cssStyleSheet) {
      if( cssStyleSheet.sheet || cssStyleSheet.styleSheet || cssStyleSheet.innerHTML ) {
        report("stylesheet", "insecure stylesheet loaded");
      }
      else {
        if(styleCount < MAX_COUNT) {
          styleCount++;
          setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);
        }
        else {
          //After we have called setTimeout the maximum number of times, assume stylesheet is blocked
          report("stylesheet", "insecure stylesheet blocked");
        }
      }
    }

    // link does not have onload and onerror events. Hence we need a setTimeout to check the link's status
    window.setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);

    // Test 1d: insecure iframe
    var iframe = document.createElement("iframe");
    var iframeId = uniqueID();
    iframe.src = baseUrl + "?type=iframe" + uniqueIDParam(iframeId);
    iframe.onload = function() {
      report("iframe", "insecure iframe loaded");
    }
    iframe.onerror = function() {
      report("iframe", "insecure iframe blocked");
    };
    testContent.appendChild(iframe);


    // Test 1e: insecure xhr
    await new Promise((resolve) => {
      var xhr = new XMLHttpRequest;
      var xhrId = uniqueID();
      try {
        xhr.open("GET", baseUrl + "?type=xhr" + uniqueIDParam(xhrId), true);
        xhr.send();
        xhr.onloadend = function (oEvent) {
          if (xhr.status == 200) {
            report("xhr", "insecure xhr loaded");
            resolve();
          }
          else {
            report("xhr", "insecure xhr blocked");
            resolve();
          }
        }
      } catch(ex) {
         report("xhr", "insecure xhr blocked");
        resolve();
      }
    });

    /* Part 2: Mixed Display tests */

    // Shorthand for all image test variants
    async function imgHandlers(img, test, uniqueID) {
      await new Promise((resolve) => {
        img.onload = async () => {
          const lastRequest = await checkLastRequest();
          is(lastRequest.uniqueID, uniqueID, "UniqueID for the last request matches");
          let message = "insecure image loaded";
          if (lastRequest.scheme == "https") {
            message = "secure image loaded after upgrade";
          }
          report(test, message);
          resolve();
        }
        img.onerror = async () => {
          let message = "insecure image blocked";
          report(test, message);
          resolve();
        }
      });
    }

    // Test 2a: insecure image
    var img = document.createElement("img");
    var imgId = uniqueID();
    img.src = baseUrl + "?type=img" + uniqueIDParam(imgId);
    await imgHandlers(img, "image", imgId);
    // We don't need to append the image to the document. Doing so causes the image test to run twice.

    // Test 2b: insecure media
    var media = document.createElement("video");
    var mediaId = uniqueID();
    media.src = baseUrl + "?type=media" + uniqueIDParam(mediaId);
    media.width = "320";
    media.height = "200";
    media.type = "video/ogg";
    await new Promise(res => {
      media.onloadeddata = async () => {
        const lastRequest = await checkLastRequest();
        is(lastRequest.uniqueID, mediaId, "UniqueID for the last request matches");
        let message = "insecure media loaded";
        if (lastRequest.scheme == "https") {
          message = "secure media loaded after upgrade";
        }
        report("media", message);
        res();
      }
      media.onerror = function() {
        report("media", "insecure media blocked");
        res();
      }
    });
    // We don't need to append the video to the document. Doing so causes the image test to run twice.

    /* Part 3: Mixed Active Tests for Image srcset */

    // Test 3a: image with srcset
    var imgA = document.createElement("img");
    var imgAId = uniqueID();
    imgA.srcset = baseUrl + "?type=img&subtype=imageSrcset" + uniqueIDParam(imgAId);
    await imgHandlers(imgA, "imageSrcset", imgAId);

    // Test 3b: image with srcset, using fallback from src, should still use imageset policy
    var imgB = document.createElement("img");
    var imgBId = uniqueID();
    imgB.srcset = " ";
    imgB.src = baseUrl + "?type=img&subtype=imageSrcSetFallback" + uniqueIDParam(imgBId);
    await imgHandlers(imgB, "imageSrcsetFallback", imgBId);

    // Test 3c: image in <picture>
    var imgC = document.createElement("img");
    var pictureC = document.createElement("picture");
    var sourceC = document.createElement("source");
    var sourceCId = uniqueID();
    sourceC.srcset = baseUrl + "?type=img&subtype=imagePicture" + uniqueIDParam(sourceCId);
    pictureC.appendChild(sourceC);
    pictureC.appendChild(imgC);
    await imgHandlers(imgC, "imagePicture", sourceCId);

    // Test 3d: Loaded basic image switching to a <picture>, loading
    //          same source, should still redo the request with new
    //          policy.
    var imgD = document.createElement("img");
    var imgDId = uniqueID();
    var sourceDId = uniqueID();
    imgD.src = baseUrl + "?type=img&subtype=imageJoinPicture" + uniqueIDParam(imgDId);
    await new Promise(res => {
      imgD.onload = imgD.onerror = function() {
        // Whether or not it loads, we want to now append it to a picture and observe
        var pictureD = document.createElement("picture");
        var sourceD = document.createElement("source");
        sourceD.srcset = baseUrl + "?type=img&subtype=imageJoinPicture2" + uniqueIDParam(sourceDId);
        pictureD.appendChild(sourceD);
        pictureD.appendChild(imgD);
        res();
      }
    });
    await imgHandlers(imgD, "imageJoinPicture", sourceDId);

    // Test 3e: img load from <picture> source reverts to img.src as it
    //          is removed -- the new request should revert to mixed
    //          display policy
    var imgE = document.createElement("img");
    var pictureE = document.createElement("picture");
    var pictureEId = uniqueID();
    var sourceE = document.createElement("source");
    var sourceEId = uniqueID();
    sourceE.srcset = baseUrl + "?type=img&subtype=imageLeavePicture" + uniqueIDParam(sourceEId);
    pictureE.appendChild(sourceE);
    pictureE.appendChild(imgE);
    imgE.src = baseUrl + "?type=img&subtype=imageLeavePicture2" + uniqueIDParam(pictureEId);
    await new Promise(res => {
      imgE.onload = imgE.onerror = function() {
        // Whether or not it loads, remove it from the picture and observe
        pictureE.removeChild(imgE)
        res();
      }
    });
    await imgHandlers(imgE, "imageLeavePicture", pictureEId);
  }

  init();

</script>
</body>
</html>
