Bug 677638 - MessagePorts in Structured Clone Algorithm, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 03 Sep 2013 14:38:51 +0200
changeset 145317 00c47683de8da5094c6cc8dd9c54e66165075507
parent 145316 3f261800748f2ae00b57819a99cecdb34378dba4
child 145318 fdefdd5262d0d206cf5b7db7c26031a9411e0d78
push id33230
push useramarchesini@mozilla.com
push dateTue, 03 Sep 2013 12:39:37 +0000
treeherdermozilla-inbound@7c90e8e1b481 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs677638
milestone26.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 677638 - MessagePorts in Structured Clone Algorithm, r=smaug
dom/base/MessagePort.cpp
dom/base/MessagePort.h
dom/base/StructuredCloneTags.h
dom/base/nsGlobalWindow.cpp
dom/base/test/Makefile.in
dom/base/test/iframe_messageChannel_cloning.html
dom/base/test/test_messageChannel_cloning.html
--- a/dom/base/MessagePort.cpp
+++ b/dom/base/MessagePort.cpp
@@ -77,10 +77,30 @@ MessagePort::Entangle(MessagePort* aMess
   MOZ_ASSERT(aMessagePort);
   MOZ_ASSERT(aMessagePort != this);
 
   Close();
 
   mEntangledPort = aMessagePort;
 }
 
+already_AddRefed<MessagePort>
+MessagePort::Clone(nsPIDOMWindow* aWindow)
+{
+  nsRefPtr<MessagePort> newPort = new MessagePort(aWindow);
+
+  // TODO Move all the events in the port message queue of original port to the
+  // port message queue of new port, if any, leaving the new port's port
+  // message queue in its initial disabled state.
+
+  if (mEntangledPort) {
+    nsRefPtr<MessagePort> port = mEntangledPort;
+    mEntangledPort = nullptr;
+
+    newPort->Entangle(port);
+    port->Entangle(newPort);
+  }
+
+  return newPort.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/MessagePort.h
+++ b/dom/base/MessagePort.h
@@ -44,16 +44,22 @@ public:
   // Non WebIDL methods
 
   // This method entangles this MessagePort with another one.
   // If it is already entangled, it's disentangled first and enatangle to the
   // new one.
   void
   Entangle(MessagePort* aMessagePort);
 
+  // Duplicate this message port. This method is used by the Structured Clone
+  // Algorithm and makes the new MessagePort active with the entangled
+  // MessagePort of this object.
+  already_AddRefed<MessagePort>
+  Clone(nsPIDOMWindow* aWindow);
+
 private:
   nsRefPtr<MessagePort> mEntangledPort;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MessagePort_h
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -23,16 +23,17 @@ enum StructuredCloneTags {
   SCTAG_DOM_FILE_WITHOUT_LASTMODIFIEDDATE,
 
   SCTAG_DOM_FILELIST,
   SCTAG_DOM_FILEHANDLE,
   SCTAG_DOM_FILE,
 
   // These tags are used for both main thread and workers.
   SCTAG_DOM_IMAGEDATA,
+  SCTAG_DOM_MESSAGEPORT,
 
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -184,16 +184,18 @@
 #ifdef MOZ_LOGGING
 // so we can get logging even in release builds
 #define FORCE_PR_LOG 1
 #endif
 #include "prlog.h"
 #include "prenv.h"
 #include "prprf.h"
 
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 
 #include "mozilla/dom/StructuredCloneTags.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadService.h"
 #endif
@@ -6745,16 +6747,17 @@ class PostMessageEvent : public nsRunnab
     nsTArray<nsCOMPtr<nsISupports> > mSupportsArray;
 };
 
 namespace {
 
 struct StructuredCloneInfo {
   PostMessageEvent* event;
   bool subsumes;
+  nsPIDOMWindow* window;
 };
 
 static JSObject*
 PostMessageReadStructuredClone(JSContext* cx,
                                JSStructuredCloneReader* reader,
                                uint32_t tag,
                                uint32_t data,
                                void* closure)
@@ -6774,16 +6777,31 @@ PostMessageReadStructuredClone(JSContext
                                                     val.address(),
                                                     getter_AddRefs(wrapper)))) {
           return JSVAL_TO_OBJECT(val);
         }
       }
     }
   }
 
+  if (tag == SCTAG_DOM_MESSAGEPORT) {
+    NS_ASSERTION(!data, "Data should be empty");
+
+    MessagePort* port;
+    if (JS_ReadBytes(reader, &port, sizeof(port))) {
+      JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+      if (global) {
+        JS::Rooted<JSObject*> obj(cx, port->WrapObject(cx, global));
+        if (JS_WrapObject(cx, obj.address())) {
+          return obj;
+        }
+      }
+    }
+  }
+
   const JSStructuredCloneCallbacks* runtimeCallbacks =
     js::GetContextStructuredCloneCallbacks(cx);
 
   if (runtimeCallbacks) {
     return runtimeCallbacks->read(cx, reader, tag, data, nullptr);
   }
 
   return nullptr;
@@ -6814,16 +6832,26 @@ PostMessageWriteStructuredClone(JSContex
       scTag = SCTAG_DOM_FILELIST;
 
     if (scTag)
       return JS_WriteUint32Pair(writer, scTag, 0) &&
              JS_WriteBytes(writer, &supports, sizeof(supports)) &&
              scInfo->event->StoreISupports(supports);
   }
 
+  MessagePort* port = nullptr;
+  nsresult rv = UNWRAP_OBJECT(MessagePort, cx, obj, port);
+  if (NS_SUCCEEDED(rv) && scInfo->subsumes) {
+    nsRefPtr<MessagePort> newPort = port->Clone(scInfo->window);
+
+    return JS_WriteUint32Pair(writer, SCTAG_DOM_MESSAGEPORT, 0) &&
+           JS_WriteBytes(writer, &newPort, sizeof(newPort)) &&
+           scInfo->event->StoreISupports(newPort);
+  }
+
   const JSStructuredCloneCallbacks* runtimeCallbacks =
     js::GetContextStructuredCloneCallbacks(cx);
 
   if (runtimeCallbacks) {
     return runtimeCallbacks->write(cx, writer, obj, nullptr);
   }
 
   return false;
@@ -6905,16 +6933,17 @@ PostMessageEvent::Run()
       return NS_OK;
   }
 
   // Deserialize the structured clone data
   JS::Rooted<JS::Value> messageData(cx);
   {
     StructuredCloneInfo scInfo;
     scInfo.event = this;
+    scInfo.window = targetWindow;
 
     if (!buffer.read(cx, messageData.address(), &kPostMessageCallbacks,
                      &scInfo)) {
       return NS_ERROR_DOM_DATA_CLONE_ERR;
     }
   }
 
   // Create the event
@@ -7051,16 +7080,17 @@ nsGlobalWindow::PostMessageMoz(const JS:
                          providedOrigin,
                          nsContentUtils::IsCallerChrome());
 
   // We *must* clone the data here, or the JS::Value could be modified
   // by script
   JSAutoStructuredCloneBuffer buffer;
   StructuredCloneInfo scInfo;
   scInfo.event = event;
+  scInfo.window = this;
 
   nsIPrincipal* principal = GetPrincipal();
   if (NS_FAILED(callerPrin->Subsumes(principal, &scInfo.subsumes)))
     return NS_ERROR_DOM_DATA_CLONE_ERR;
 
   if (!buffer.write(aCx, aMessage, aTransfer, &kPostMessageCallbacks, &scInfo))
     return NS_ERROR_DOM_DATA_CLONE_ERR;
 
--- a/dom/base/test/Makefile.in
+++ b/dom/base/test/Makefile.in
@@ -27,16 +27,18 @@ MOCHITEST_FILES = \
   test_window_indexing.html \
   test_writable-replaceable.html \
   test_domcursor.html \
   test_named_frames.html \
   test_Image_constructor.html \
   test_setting_opener.html \
   test_error.html \
   test_messageChannel.html \
+  test_messageChannel_cloning.html \
+  iframe_messageChannel_cloning.html \
   $(NULL)
 
 MOCHITEST_CHROME_FILES = \
    test_bug715041.xul \
    test_bug715041_removal.xul \
    test_domrequesthelper.xul \
    $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/dom/base/test/iframe_messageChannel_cloning.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <script type="application/javascript">
+
+  function ok(a, msg) {
+    window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*");
+  }
+
+  window.addEventListener('message', receiveMessage, false);
+  function receiveMessage(evt) {
+    ok (evt.data, "Data received");
+    ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort");
+
+    var a = new MessageChannel();
+    window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*');
+  }
+
+  </script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_messageChannel_cloning.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677638
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 677638 - port cloning</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677638">Mozilla Bug 677638</a>
+<div id="content"></div>
+<pre id="test">
+</pre>
+  <script type="application/javascript">
+
+  // This test checks if MessagePorts can be shared with iframes
+  function test_iframe() {
+    window.addEventListener('message', receiveMessage, false);
+    function receiveMessage(evt) {
+      if (evt.data.status == 'OK') {
+        ok(true, evt.data.message);
+      } else if (evt.data.status == 'KO') {
+        ok(false, evt.data.message);
+      } else if (evt.data.status == 'FINISH') {
+        ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort");
+        window.removeEventListener('message', receiveMessage);
+        runTest();
+      } else {
+        ok(false, "Unknown message");
+      }
+    }
+
+    var a = new MessageChannel();
+    ok(a, "MessageChannel created");
+
+    var div = document.getElementById("content");
+    ok(div, "Parent exists");
+
+    var ifr = document.createElement("iframe");
+    ifr.addEventListener("load", iframeLoaded, false);
+    ifr.setAttribute('src', "iframe_messageChannel_cloning.html");
+    div.appendChild(ifr);
+
+    function iframeLoaded() {
+      ifr.contentWindow.postMessage({ port: a.port2 }, '*');
+    }
+  }
+
+  var tests = [
+    test_iframe
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  runTest();
+  </script>
+</body>
+</html>