dom/base/test/test_postMessages.html
author Andrea Marchesini <amarchesini@mozilla.com>
Tue, 12 Apr 2016 08:50:38 -0400
changeset 330680 b416fc68c0a2d4ea2640a31826233aa053c058c8
parent 327416 6befcf45fa6cf0e96bb4a24bfaf3bd6219f3d198
child 330681 83ce34cabf08a9b9d2ddc214c5cf880a81536107
permissions -rw-r--r--
Bug 1257180 - patch 1 - Directory clonable to workers, r=smaug

<!DOCTYPE HTML>
<html>
<head>
  <title>Test for postMessages cloning/transferring objects</title>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>

<body>
<input id="fileList" type="file"></input>
<script type="application/javascript;version=1.7">

function getType(a) {
  if (a === null || a === undefined)
    return 'null';

  if (Array.isArray(a))
    return 'array';

  if (typeof a == 'object')
    return 'object';

  return 'primitive';
}

function compare(a, b) {
  is (getType(a), getType(b), 'Type matches');

  var type = getType(a);
  if (type == 'array') {
    is (a.length, b.length, 'Array.length matches');
    for (var i = 0; i < a.length; ++i) {
      compare(a[i], b[i]);
    }

    return;
  }

  if (type == 'object') {
    ok (a !== b, 'They should not match');

    var aProps = [];
    for (var p in a) aProps.push(p);

    var bProps = [];
    for (var p in b) bProps.push(p);

    is (aProps.length, bProps.length, 'Props match');
    is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()');

    for (var p in a) {
      compare(a[p], b[p]);
    }

    return;
  }

  if (type != 'null') {
    is (a.toSource(), b.toSource(), 'Matching using toSource()');
  }
}

var clonableObjects = [
  'hello world',
  123,
  null,
  true,
  new Date(),
  [ 1, 'test', true, new Date() ],
  { a: true, b:  null, c: new Date(), d: [ true, false, {} ] },
  new Blob([123], { type: 'plain/text' }),
  new ImageData(2, 2),
];

function create_fileList_forFile() {
  var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
  var script = SpecialPowers.loadChromeScript(url);

  function onOpened(message) {
    var fileList = document.getElementById('fileList');
    SpecialPowers.wrap(fileList).mozSetFileArray([message.file]);

    // Just a simple test
    var domFile = fileList.files[0];
    is(domFile.name, "prefs.js", "fileName should be prefs.js");

    clonableObjects.push(fileList.files);
    script.destroy();
    next();
  }

  script.addMessageListener("file.opened", onOpened);
  script.sendAsyncMessage("file.open");
}

function create_fileList_forDir() {
  var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js");
  var script = SpecialPowers.loadChromeScript(url);

  function onOpened(message) {
    var fileList = document.getElementById('fileList');
    SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);

    // Just a simple test
    is(fileList.files.length, 1, "Filelist has 1 element");
    ok(fileList.files[0] instanceof Directory, "We have a directory.");

    clonableObjects.push(fileList.files);
    script.destroy();
    next();
  }

  script.addMessageListener("dir.opened", onOpened);
  script.sendAsyncMessage("dir.open");
}

function runTests(obj) {
  ok(('clonableObjects' in obj) &&
     ('transferableObjects' in obj) &&
     (obj.clonableObjects || obj.transferableObjects), "We must run some test!");

  // cloning tests
  new Promise(function(resolve, reject) {
    if (!obj.clonableObjects) {
      resolve();
      return;
    }

    var clonableObjectsId = 0;
    function runClonableTest() {
      if (clonableObjectsId >= clonableObjects.length) {
        resolve();
        return;
      }

      var object = clonableObjects[clonableObjectsId++];

      obj.send(object, []).then(function(received) {
        compare(received.data, object);
        runClonableTest();
      });
    }

    runClonableTest();
  })

  // transfering tests
  .then(function() {
    if (!obj.transferableObjects) {
      return;
    }

    // MessagePort
    return new Promise(function(r, rr) {
      var mc = new MessageChannel();
      obj.send(42, [mc.port1]).then(function(received) {
        ok(received.ports.length, 1, "MessagePort has been transferred");
        mc.port2.postMessage("hello world");
        received.ports[0].onmessage = function(e) {
          ok(e.data, "hello world", "Ports are connected!");
          r();
        }
      });
    });
  })

  // no dup transfering
  .then(function() {
    if (!obj.transferableObjects) {
      return;
    }

    // MessagePort
    return new Promise(function(r, rr) {
      var mc = new MessageChannel();
      obj.send(42, [mc.port1, mc.port1]).then(function(received) {
        ok(false, "Duplicate ports should throw!");
      }, function() {
        ok(true, "Duplicate ports should throw!");
      })
      .then(r);
    });
  })

  // non transfering tests
  .then(function() {
    if (obj.transferableObjects) {
      return;
    }

    // MessagePort
    return new Promise(function(r, rr) {
      var mc = new MessageChannel();
      obj.send(42, [mc.port1]).then(function(received) {
        ok(false, "This object should not support port transferring");
      }, function() {
        ok(true, "This object should not support port transferring");
      })
      .then(r);
    });
  })

  // done.
  .then(function() {
    obj.finished();
  });
}

// PostMessage to the same window.
function test_windowToWindow() {
  info("Testing window to window");
  var resolve;

  onmessage = function(e) {
    if (!resolve) {
      ok(false, "Unexpected message!");
      return;
    }

    let tmp = resolve;
    resolve = null;
    tmp({ data: e.data, ports: e.ports });
  }

  runTests({
    clonableObjects: true,
    transferableObjects: true,
    send: function(what, ports) {
      return new Promise(function(r, rr) {
        resolve = r;

        try {
          postMessage(what, '*', ports);
        } catch(e) {
          resolve = null;
          rr();
        }
      });
    },

    finished: function() {
      onmessage = null;
      next();
    }
  });
}

// PostMessage to iframe
function test_windowToIframe() {
  info("Testing window to iframe");
  test_windowToIframeURL('iframe_postMessages.html');
}

// PostMessage to cross-origin iframe
function test_windowToCrossOriginIframe() {
  info("Testing window to cross-origin iframe");
  test_windowToIframeURL('http://example.com/tests/dom/base/test/iframe_postMessages.html');
}

// iframe helper class
function test_windowToIframeURL(url) {
  var resolve;

  onmessage = function(e) {
    if (!resolve) {
      ok(false, "Unexpected message!");
      return;
    }

    let tmp = resolve;
    resolve = null;
    tmp({ data: e.data, ports: e.ports });
  }

  var ifr = document.createElement('iframe');
  ifr.src = url;
  ifr.onload = function() {
    runTests({
      clonableObjects: true,
      transferableObjects: true,
      send: function(what, ports) {
        return new Promise(function(r, rr) {
          resolve = r;
          try {
            ifr.contentWindow.postMessage(what, '*', ports);
          } catch(e) {
            resolve = null;
            rr();
          }
        });
      },

      finished: function() {
        document.body.removeChild(ifr);
        onmessage = null;
        next();
      }
    });
  }
  document.body.appendChild(ifr);
}

// PostMessage for Workers
function test_workers() {
  info("Testing Workers");

  var resolve;

  var w = new Worker('worker_postMessages.js');
  w.postMessage('workers');
  w.onmessage = function(e) {
    is(e.data, 'ok', "Worker ready!");

    w.onmessage = function(e) {
      if (!resolve) {
        ok(false, "Unexpected message!");
        return;
      }

      let tmp = resolve;
      resolve = null;
      tmp({ data: e.data, ports: e.ports });
    }

    runTests({
      clonableObjects: true,
      transferableObjects: true,
      send: function(what, ports) {
        return new Promise(function(r, rr) {
          resolve = r;
          try {
            w.postMessage(what, ports);
          } catch(e) {
            resolve = null;
            rr();
          }
        });
      },

      finished: function() {
        onmessage = null;
        next();
      }
    });
  }
}

// PostMessage for BroadcastChannel
function test_broadcastChannel() {
  info("Testing broadcastChannel");

  var bc1 = new BroadcastChannel('postMessagesTest');
  var bc2 = new BroadcastChannel('postMessagesTest');

  var resolve;

  bc2.onmessage = function(e) {
    if (!resolve) {
      ok(false, "Unexpected message!");
      return;
    }

    let tmp = resolve;
    resolve = null;
    tmp({ data: e.data, ports: [] });
  }

  runTests({
    clonableObjects: true,
    transferableObjects: false,
    send: function(what, ports) {
      return new Promise(function(r, rr) {
        if (ports.length) {
          rr();
          return;
        }

        resolve = r;
        bc1.postMessage(what);
      });
    },

    finished: function() {
      onmessage = null;
      next();
    }
  });
}

// PostMessage for BroadcastChannel in workers
function test_broadcastChannel_inWorkers() {
  info("Testing broadcastChannel in Workers");

  var bc = new BroadcastChannel('postMessagesTest_inWorkers');
  var resolve;

  var w = new Worker('worker_postMessages.js');
  w.postMessage('broadcastChannel');
  w.onmessage = function(e) {
    is(e.data, 'ok', "Worker ready!");

    w.onmessage = function(e) {
      if (!resolve) {
        ok(false, "Unexpected message!");
        return;
      }

      let tmp = resolve;
      resolve = null;
      tmp({ data: e.data, ports: e.ports });
    }

    runTests({
      clonableObjects: true,
      transferableObjects: false,
      send: function(what, ports) {
        return new Promise(function(r, rr) {
          if (ports.length) {
            rr();
            return;
          }

          resolve = r;
          bc.postMessage(what);
        });
      },

      finished: function() {
        onmessage = null;
        next();
      }
    });
  }
}

// PostMessage for MessagePort
function test_messagePort() {
  info("Testing messagePort");

  var mc = new MessageChannel();
  var resolve;

  mc.port2.onmessage = function(e) {
    if (!resolve) {
      ok(false, "Unexpected message!");
      return;
    }

    let tmp = resolve;
    resolve = null;
    tmp({ data: e.data, ports: e.ports });
  }

  runTests({
    clonableObjects: true,
    transferableObjects: true,
    send: function(what, ports) {
      return new Promise(function(r, rr) {
        resolve = r;
        try {
          mc.port1.postMessage(what, ports);
        } catch(e) {
          resolve = null;
          rr();
        }
      });
    },

    finished: function() {
      onmessage = null;
      next();
    }
  });
}

// PostMessage for MessagePort in Workers
function test_messagePort_inWorkers() {
  info("Testing messagePort in workers");

  var mc = new MessageChannel();
  var resolve;

  var w = new Worker('worker_postMessages.js');
  w.postMessage('messagePort', [ mc.port2 ]);
  w.onmessage = function(e) {
    is(e.data, 'ok', "Worker ready!");

    w.onmessage = function(e) {
      if (!resolve) {
        ok(false, "Unexpected message!");
        return;
      }

      let tmp = resolve;
      resolve = null;
      tmp({ data: e.data, ports: e.ports });
    }

    runTests({
      clonableObjects: true,
      transferableObjects: true,
      send: function(what, ports) {
        return new Promise(function(r, rr) {
          resolve = r;
          try {
            mc.port1.postMessage(what, ports);
          } catch(e) {
            resolve = null;
            rr();
          }
        });
      },

      finished: function() {
        onmessage = null;
        next();
      }
    });
  }
}

var tests = [
  create_fileList_forFile,
  create_fileList_forDir,

  test_windowToWindow,
  test_windowToIframe,
  test_windowToCrossOriginIframe,

  test_workers,

  test_broadcastChannel,
  test_broadcastChannel_inWorkers,

  test_messagePort,
  test_messagePort_inWorkers,
];

function next() {
  if (!tests.length) {
    SimpleTest.finish();
    return;
  }

  var test = tests.shift();
  test();
}

SimpleTest.waitForExplicitFinish();
next();
</script>
</body>
</html>