Bug 1186544 - A better test suite for StructuredCloneAlgorithm, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 28 Jul 2015 08:37:37 +0100
changeset 286570 03bb7cc89e2c0b8b7df44df3b8a691a2a30f1353
parent 286569 6e4003eb5eabc9770f5d5eea81ff3ccb09f26d38
child 286571 2329a3c7a96471337541b719a323b4a994af5e6b
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1186544
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 1186544 - A better test suite for StructuredCloneAlgorithm, r=smaug
dom/base/nsJSEnvironment.cpp
dom/base/test/iframe_cloning_fileList.html
dom/base/test/iframe_postMessages.html
dom/base/test/mochitest.ini
dom/base/test/script_cloning_fileList.js
dom/base/test/script_postmessages_fileList.js
dom/base/test/test_cloning_fileList.html
dom/base/test/test_postMessages.html
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2488,36 +2488,48 @@ JSObject*
 NS_DOMReadStructuredClone(JSContext* cx,
                           JSStructuredCloneReader* reader,
                           uint32_t tag,
                           uint32_t data,
                           void* closure)
 {
   if (tag == SCTAG_DOM_IMAGEDATA) {
     return ReadStructuredCloneImageData(cx, reader);
-  } else if (tag == SCTAG_DOM_WEBCRYPTO_KEY) {
+  }
+
+  if (tag == SCTAG_DOM_WEBCRYPTO_KEY) {
+    if (!NS_IsMainThread()) {
+      return nullptr;
+    }
+
     nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
     if (!global) {
       return nullptr;
     }
 
     // Prevent the return value from being trashed by a GC during ~nsRefPtr.
     JS::Rooted<JSObject*> result(cx);
     {
       nsRefPtr<CryptoKey> key = new CryptoKey(global);
       if (!key->ReadStructuredClone(reader)) {
         result = nullptr;
       } else {
         result = key->WrapObject(cx, nullptr);
       }
     }
     return result;
-  } else if (tag == SCTAG_DOM_NULL_PRINCIPAL ||
-             tag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
-             tag == SCTAG_DOM_CONTENT_PRINCIPAL) {
+  }
+
+  if (tag == SCTAG_DOM_NULL_PRINCIPAL ||
+      tag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
+      tag == SCTAG_DOM_CONTENT_PRINCIPAL) {
+    if (!NS_IsMainThread()) {
+      return nullptr;
+    }
+
     mozilla::ipc::PrincipalInfo info;
     if (tag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
       info = mozilla::ipc::SystemPrincipalInfo();
     } else if (tag == SCTAG_DOM_NULL_PRINCIPAL) {
       info = mozilla::ipc::NullPrincipalInfo();
     } else {
       uint32_t appId = data;
 
@@ -2545,58 +2557,64 @@ NS_DOMReadStructuredClone(JSContext* cx,
     JS::RootedValue result(cx);
     rv = nsContentUtils::WrapNative(cx, principal, &NS_GET_IID(nsIPrincipal), &result);
     if (NS_FAILED(rv)) {
       xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
       return nullptr;
     }
 
     return result.toObjectOrNull();
-  } else if (tag == SCTAG_DOM_NFC_NDEF) {
+  }
+
 #ifdef MOZ_NFC
+  if (tag == SCTAG_DOM_NFC_NDEF) {
+    if (!NS_IsMainThread()) {
+      return nullptr;
+    }
+
     nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
     if (!global) {
       return nullptr;
     }
 
     // Prevent the return value from being trashed by a GC during ~nsRefPtr.
     JS::Rooted<JSObject*> result(cx);
     {
       nsRefPtr<MozNDEFRecord> ndefRecord = new MozNDEFRecord(global);
       result = ndefRecord->ReadStructuredClone(cx, reader) ?
                ndefRecord->WrapObject(cx, nullptr) : nullptr;
     }
     return result;
-#else
-    return nullptr;
+  }
 #endif
-  }
-
+
+#ifdef MOZ_WEBRTC
   if (tag == SCTAG_DOM_RTC_CERTIFICATE) {
-#ifdef MOZ_WEBRTC
+    if (!NS_IsMainThread()) {
+      return nullptr;
+    }
+
     nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
     if (!global) {
       return nullptr;
     }
 
     // Prevent the return value from being trashed by a GC during ~nsRefPtr.
     JS::Rooted<JSObject*> result(cx);
     {
       nsRefPtr<RTCCertificate> cert = new RTCCertificate(global);
       if (!cert->ReadStructuredClone(reader)) {
         result = nullptr;
       } else {
         result = cert->WrapObject(cx, nullptr);
       }
     }
     return result;
-#else
-    return nullptr;
+  }
 #endif
-  }
 
   // Don't know what this is. Bail.
   xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return nullptr;
 }
 
 bool
 NS_DOMWriteStructuredClone(JSContext* cx,
@@ -2608,30 +2626,32 @@ NS_DOMWriteStructuredClone(JSContext* cx
   ImageData* imageData;
   if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, obj, imageData))) {
     return WriteStructuredCloneImageData(cx, writer, imageData);
   }
 
   // Handle Key cloning
   CryptoKey* key;
   if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, obj, key))) {
+    MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread.");
     return JS_WriteUint32Pair(writer, SCTAG_DOM_WEBCRYPTO_KEY, 0) &&
            key->WriteStructuredClone(writer);
   }
 
 #ifdef MOZ_WEBRTC
   // Handle WebRTC Certificate cloning
   RTCCertificate* cert;
   if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, obj, cert))) {
+    MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread.");
     return JS_WriteUint32Pair(writer, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
            cert->WriteStructuredClone(writer);
   }
 #endif
 
-  if (xpc::IsReflector(obj)) {
+  if (NS_IsMainThread() && xpc::IsReflector(obj)) {
     nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(obj);
     nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
     if (principal) {
       mozilla::ipc::PrincipalInfo info;
       if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(principal, &info)))) {
         xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
         return false;
       }
@@ -2649,16 +2669,17 @@ NS_DOMWriteStructuredClone(JSContext* cx
              JS_WriteUint32Pair(writer, cInfo.isInBrowserElement(), cInfo.spec().Length()) &&
              JS_WriteBytes(writer, cInfo.spec().get(), cInfo.spec().Length());
     }
   }
 
 #ifdef MOZ_NFC
   MozNDEFRecord* ndefRecord;
   if (NS_SUCCEEDED(UNWRAP_OBJECT(MozNDEFRecord, obj, ndefRecord))) {
+    MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread.");
     return JS_WriteUint32Pair(writer, SCTAG_DOM_NFC_NDEF, 0) &&
            ndefRecord->WriteStructuredClone(cx, writer);
   }
 #endif // MOZ_NFC
 
   // Don't know what this is
   xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return false;
rename from dom/base/test/iframe_cloning_fileList.html
rename to dom/base/test/iframe_postMessages.html
--- a/dom/base/test/iframe_cloning_fileList.html
+++ b/dom/base/test/iframe_postMessages.html
@@ -1,10 +1,10 @@
 <!DOCTYPE HTML>
 <html>
 <body>
 <script>
 onmessage = function(e) {
-  parent.postMessage(e.data, '*');
+  parent.postMessage(e.data, '*', e.ports);
 }
 </script>
 </body>
 </html>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -237,16 +237,18 @@ support-files =
   referrerHelper.js
   test_performance_user_timing.js
   img_referrer_testserver.sjs
   file_audioLoop.html
   file_webaudioLoop.html
   file_webaudioLoop2.html
   referrer_helper.js
   referrer_testserver.sjs
+  script_postmessages_fileList.js
+  iframe_postMessages.html
 
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_appname_override.html]
 [test_async_setTimeout_stack.html]
 [test_async_setTimeout_stack_across_globals.html]
@@ -800,10 +802,9 @@ skip-if = buildapp == 'mulet' || buildap
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
 [test_integer_attr_with_leading_zero.html]
 [test_getAttribute_after_createAttribute.html]
 [test_script_loader_crossorigin_data_url.html]
 [test_file_negative_date.html]
 [test_nonascii_blob_url.html]
 [test_window_element_enumeration.html]
 [test_referrer_redirect.html]
-[test_cloning_fileList.html]
-support-files = script_cloning_fileList.js iframe_cloning_fileList.html
+[test_postMessages.html]
rename from dom/base/test/script_cloning_fileList.js
rename to dom/base/test/script_postmessages_fileList.js
deleted file mode 100644
--- a/dom/base/test/test_cloning_fileList.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for cloning FileList</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-
-<body>
-<p id="display">
-  <input id="fileList" type="file"></input>
-</p>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-var iframeScriptURL;
-
-var url = SimpleTest.getTestFileURL("script_cloning_fileList.js");
-var script = SpecialPowers.loadChromeScript(url);
-script.addMessageListener("file.opened", onOpened);
-
-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");
-
-  var ifr = document.createElement('iframe');
-  ifr.src = iframeScriptURL;
-  ifr.onload = function() {
-    ifr.contentWindow.postMessage(fileList.files, "*");
-  }
-  document.body.appendChild(ifr);
-}
-
-function runTest() {
-  script.sendAsyncMessage("file.open");
-}
-
-onmessage = function(e) {
-  var fileList = document.getElementById('fileList');
-
-  ok(true, "Message received");
-  ok(e.data instanceof FileList, "The object is a FileList");
-  ok(e.data != fileList.files, "The object has been cloned!");
-  is(e.data.length, fileList.files.length, "The length matches");
-  is(e.data.length, 1, "1 element found!");
-  is(e.data[0].name, "prefs.js", "fileName should be prefs.js");
-
-  next();
-}
-
-var tests = [
-  function() {
-    // Same origin
-    iframeScriptURL = 'iframe_cloning_fileList.html';
-    runTest();
-  },
-
-  function() {
-    // Cross Origin
-    iframeScriptURL = 'http://example.com/tests/dom/base/test/iframe_cloning_fileList.html';
-    runTest();
-  }
-];
-
-function next() {
-  if (!tests.length) {
-    script.destroy();
-    SimpleTest.finish();
-    return;
-  }
-
-  var test = tests.shift();
-  test();
-}
-
-SimpleTest.waitForExplicitFinish();
-next();
-</script>
-</pre>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_postMessages.html
@@ -0,0 +1,256 @@
+<!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() {
+  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 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();
+        }
+      });
+    });
+  })
+
+  // 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;
+        postMessage(what, '*', ports);
+      });
+    },
+
+    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;
+          ifr.contentWindow.postMessage(what, '*', ports);
+        });
+      },
+
+      finished: function() {
+        document.body.removeChild(ifr);
+        onmessage = null;
+        next();
+      }
+    });
+  }
+  document.body.appendChild(ifr);
+}
+
+var tests = [
+  create_fileList,
+
+  test_windowToWindow,
+  test_windowToIframe,
+  test_windowToCrossOriginIframe,
+];
+
+function next() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.messageChannel.enabled", true]]}, next);
+</script>
+</body>
+</html>