Bug 618484 - 'Allow ChromeWorkers access to XPCOM objects'. r=jst+jorendorff, a=blocking.
authorBen Turner <bent.mozilla@gmail.com>
Thu, 06 Jan 2011 22:21:30 -0800
changeset 60105 5e2045dddd6714941385a667ea0ce43bcfcd2510
parent 60104 40d3ac45451f9dd045ad801e57bbf2046e1db9f3
child 60106 9b05e0e4cf040fcb64667080f0f09472d9e5c1d4
push id17864
push userbturner@mozilla.com
push dateFri, 07 Jan 2011 06:22:28 +0000
treeherdermozilla-central@4cb3d8a21afe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, blocking
bugs618484
milestone2.0b9pre
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 618484 - 'Allow ChromeWorkers access to XPCOM objects'. r=jst+jorendorff, a=blocking.
dom/base/nsJSEnvironment.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/src/threads/nsDOMThreadService.cpp
dom/src/threads/nsDOMThreadService.h
dom/src/threads/nsDOMWorker.cpp
dom/src/threads/nsDOMWorker.h
dom/src/threads/nsDOMWorkerEvents.cpp
dom/src/threads/nsDOMWorkerEvents.h
dom/src/threads/nsDOMWorkerSecurityManager.cpp
dom/src/threads/test/Makefile.in
dom/src/threads/test/chromeWorker_worker.js
dom/src/threads/test/test_chromeWorker.xul
dom/src/threads/test/test_chromeWorkerComponent.xul
dom/src/threads/test/test_xpcom.html
dom/src/threads/test/xpcom_worker.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsclone.cpp
js/src/jsclone.h
js/src/jspubtd.h
js/src/shell/js.cpp
js/src/shell/jsworkers.cpp
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -4059,27 +4059,29 @@ ObjectPrincipalFinder(JSContext *cx, JSO
 
   return jsPrincipals;
 }
 
 static JSObject*
 DOMReadStructuredClone(JSContext* cx,
                        JSStructuredCloneReader* reader,
                        uint32 tag,
-                       uint32 data)
+                       uint32 data,
+                       void* closure)
 {
   // We don't currently support any extensions to structured cloning.
   nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return nsnull;
 }
 
 static JSBool
 DOMWriteStructuredClone(JSContext* cx,
                         JSStructuredCloneWriter* writer,
-                        JSObject* obj)
+                        JSObject* obj,
+                        void *closure)
 {
   // We don't currently support any extensions to structured cloning.
   nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_DOM_DATA_CLONE_ERR);
   return JS_FALSE;
 }
 
 static void
 DOMStructuredCloneError(JSContext* cx,
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -528,17 +528,17 @@ IDBObjectStore::GetKeyPathValueFromStruc
   }
   JSContext*& cx = *aCx;
 
   JSAutoRequest ar(cx);
 
   jsval clone;
   if (!JS_ReadStructuredClone(cx, reinterpret_cast<const uint64*>(aData),
                               aDataLength, JS_STRUCTURED_CLONE_VERSION,
-                              &clone)) {
+                              &clone, NULL, NULL)) {
     return NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
   if (JSVAL_IS_PRIMITIVE(clone)) {
     // This isn't an object, so just leave the key unset.
     aValue = Key::UNSETKEY;
     return NS_OK;
   }
--- a/dom/src/threads/nsDOMThreadService.cpp
+++ b/dom/src/threads/nsDOMThreadService.cpp
@@ -750,16 +750,19 @@ nsDOMThreadService::Init()
   NS_ENSURE_TRUE(mMonitor, NS_ERROR_OUT_OF_MEMORY);
 
   PRBool success = mWorkersInProgress.Init();
   NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
   success = mPools.Init();
   NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
+  success = mThreadsafeContractIDs.Init();
+  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
   success = mJSContexts.SetCapacity(THREADPOOL_THREAD_CAP);
   NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
 
   nsCOMPtr<nsIJSRuntimeService>
     runtimeSvc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1"));
   NS_ENSURE_TRUE(runtimeSvc, NS_ERROR_FAILURE);
   runtimeSvc.forget(&gJSRuntimeService);
 
@@ -1211,16 +1214,53 @@ nsDOMThreadService::ChangeThreadPoolMaxT
       rv = mThreadPool->Dispatch(dummy, NS_DISPATCH_NORMAL);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
+void
+nsDOMThreadService::NoteThreadsafeContractId(const nsACString& aContractId,
+                                             PRBool aIsThreadsafe)
+{
+  NS_ASSERTION(!aContractId.IsEmpty(), "Empty contract id!");
+
+  nsAutoMonitor mon(mMonitor);
+
+#ifdef DEBUG
+  {
+    PRBool isThreadsafe;
+    if (mThreadsafeContractIDs.Get(aContractId, &isThreadsafe)) {
+      NS_ASSERTION(aIsThreadsafe == isThreadsafe, "Inconsistent threadsafety!");
+    }
+  }
+#endif
+
+  if (!mThreadsafeContractIDs.Put(aContractId, aIsThreadsafe)) {
+    NS_WARNING("Out of memory!");
+  }
+}
+
+ThreadsafeStatus
+nsDOMThreadService::GetContractIdThreadsafeStatus(const nsACString& aContractId)
+{
+  NS_ASSERTION(!aContractId.IsEmpty(), "Empty contract id!");
+
+  nsAutoMonitor mon(mMonitor);
+
+  PRBool isThreadsafe;
+  if (mThreadsafeContractIDs.Get(aContractId, &isThreadsafe)) {
+    return isThreadsafe ? Threadsafe : NotThreadsafe;
+  }
+
+  return Unknown;
+}
+
 // static
 nsIJSRuntimeService*
 nsDOMThreadService::JSRuntimeService()
 {
   return gJSRuntimeService;
 }
 
 // static
--- a/dom/src/threads/nsDOMThreadService.h
+++ b/dom/src/threads/nsDOMThreadService.h
@@ -44,16 +44,17 @@
 #include "nsIEventTarget.h"
 #include "nsIObserver.h"
 #include "nsIThreadPool.h"
 
 // Other includes
 #include "jsapi.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
 #include "nsRefPtrHashtable.h"
 #include "nsStringGlue.h"
 #include "nsTPtrArray.h"
 #include "prmon.h"
 
 #include "prlog.h"
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gDOMThreadsLog;
@@ -64,16 +65,23 @@ class nsDOMWorkerPool;
 class nsDOMWorkerRunnable;
 class nsDOMWorkerTimeout;
 class nsIJSRuntimeService;
 class nsIScriptGlobalObject;
 class nsIThreadJSContextStack;
 class nsIXPConnect;
 class nsIXPCSecurityManager;
 
+enum ThreadsafeStatus
+{
+  Threadsafe,
+  NotThreadsafe,
+  Unknown
+};
+
 class nsDOMThreadService : public nsIEventTarget,
                            public nsIObserver,
                            public nsIThreadPoolListener
 {
   friend class nsDOMWorker;
   friend class nsDOMWorkerNavigator;
   friend class nsDOMWorkerPool;
   friend class nsDOMWorkerRunnable;
@@ -109,16 +117,21 @@ public:
   static nsIXPCSecurityManager* WorkerSecurityManager();
 
   void CancelWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void SuspendWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
   void ResumeWorkersForGlobal(nsIScriptGlobalObject* aGlobalObject);
 
   nsresult ChangeThreadPoolMaxThreads(PRInt16 aDelta);
 
+  void NoteThreadsafeContractId(const nsACString& aContractId,
+                                PRBool aIsThreadsafe);
+
+  ThreadsafeStatus GetContractIdThreadsafeStatus(const nsACString& aContractId);
+
 private:
   nsDOMThreadService();
   ~nsDOMThreadService();
 
   nsresult Init();
   void Cleanup();
 
   static void Shutdown();
@@ -180,16 +193,19 @@ private:
   // A list of active JSContexts that we've created. Always protected with
   // mMonitor.
   nsTArray<JSContext*> mJSContexts;
 
   // A list of worker runnables that were never started because the worker was
   // suspended. Always protected with mMonitor.
   nsTArray<nsDOMWorkerRunnable*> mSuspendedWorkers;
 
+  // Always protected with mMonitor.
+  nsDataHashtable<nsCStringHashKey, PRBool> mThreadsafeContractIDs;
+
   nsString mAppName;
   nsString mAppVersion;
   nsString mPlatform;
   nsString mUserAgent;
 
   PRBool mNavigatorStringsLoaded;
 };
 
--- a/dom/src/threads/nsDOMWorker.cpp
+++ b/dom/src/threads/nsDOMWorker.cpp
@@ -46,16 +46,17 @@
 #include "jscntxt.h"
 #ifdef MOZ_SHARK
 #include "jsdbgapi.h"
 #endif
 #include "nsAtomicRefcnt.h"
 #include "nsAutoLock.h"
 #include "nsAXPCNativeCallContext.h"
 #include "nsContentUtils.h"
+#include "nsDOMClassInfo.h"
 #include "nsDOMClassInfoID.h"
 #include "nsGlobalWindow.h"
 #include "nsJSON.h"
 #include "nsJSUtils.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "nsNativeCharsetUtils.h"
 #include "xpcprivate.h"
@@ -64,16 +65,67 @@
 #include "nsDOMWorkerEvents.h"
 #include "nsDOMWorkerLocation.h"
 #include "nsDOMWorkerNavigator.h"
 #include "nsDOMWorkerPool.h"
 #include "nsDOMWorkerScriptLoader.h"
 #include "nsDOMWorkerTimeout.h"
 #include "nsDOMWorkerXHR.h"
 
+class TestComponentThreadsafetyRunnable : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  TestComponentThreadsafetyRunnable(const nsACString& aContractId,
+                                    PRBool aService)
+  : mContractId(aContractId),
+    mService(aService),
+    mIsThreadsafe(PR_FALSE)
+  { }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    nsresult rv;
+    nsCOMPtr<nsISupports> instance;
+    if (mService) {
+      instance = do_GetService(mContractId.get(), &rv);
+    }
+    else {
+      instance = do_CreateInstance(mContractId.get(), &rv);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(instance, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRUint32 flags;
+    rv = classInfo->GetFlags(&flags);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mIsThreadsafe = !!(flags & nsIClassInfo::THREADSAFE);
+    return NS_OK;
+  }
+
+  PRBool IsThreadsafe()
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+    return mIsThreadsafe;
+  }
+
+private:
+  nsCString mContractId;
+  PRBool mService;
+  PRBool mIsThreadsafe;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(TestComponentThreadsafetyRunnable, nsIRunnable)
+
 class nsDOMWorkerFunctions
 {
 public:
   typedef nsDOMWorker::WorkerPrivilegeModel WorkerPrivilegeModel;
 
   // Same as window.dump().
   static JSBool
   Dump(JSContext* aCx, uintN aArgc, jsval* aVp);
@@ -110,29 +162,51 @@ public:
 
   static JSBool
   BtoA(JSContext* aCx, uintN aArgc, jsval* aVp);
 
   // Chrome-only functions
   static JSBool
   NewChromeWorker(JSContext* aCx, uintN aArgc, jsval* aVp);
 
+  static JSBool
+  XPCOMLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp);
+
+  static JSBool
+  CreateInstance(JSContext* aCx, uintN aArgc, jsval* aVp) {
+    return GetInstanceCommon(aCx, aArgc, aVp, PR_FALSE);
+  }
+
+  static JSBool
+  GetService(JSContext* aCx, uintN aArgc, jsval* aVp) {
+    return GetInstanceCommon(aCx, aArgc, aVp, PR_TRUE);
+  }
+
 #ifdef BUILD_CTYPES
   static JSBool
   CTypesLazyGetter(JSContext* aCx, JSObject* aObj, jsid aId, jsval* aVp);
 #endif
 
 private:
   // Internal helper for SetTimeout and SetInterval.
   static JSBool
   MakeTimeout(JSContext* aCx, uintN aArgc, jsval* aVp, PRBool aIsInterval);
 
   static JSBool
   MakeNewWorker(JSContext* aCx, uintN aArgc, jsval* aVp,
                 WorkerPrivilegeModel aPrivilegeModel);
+
+  static JSBool
+  GetInstanceCommon(JSContext* aCx, uintN aArgc, jsval* aVp, PRBool aService);
+};
+
+JSFunctionSpec gDOMWorkerXPCOMFunctions[] = {
+  {"createInstance", nsDOMWorkerFunctions::CreateInstance, 1, JSPROP_ENUMERATE},
+  {"getService",     nsDOMWorkerFunctions::GetService,     1, JSPROP_ENUMERATE},
+  { nsnull,          nsnull,                               0, 0 }
 };
 
 JSBool
 nsDOMWorkerFunctions::Dump(JSContext* aCx,
                            uintN aArgc,
                            jsval* aVp)
 {
   JS_SET_RVAL(cx, aVp, JSVAL_VOID);
@@ -411,16 +485,179 @@ nsDOMWorkerFunctions::NewChromeWorker(JS
     JS_ReportError(aCx, "Cannot create a priviliged worker!");
     return JS_FALSE;
   }
 
   return MakeNewWorker(aCx, aArgc, aVp, nsDOMWorker::CHROME);
 }
 
 JSBool
+nsDOMWorkerFunctions::XPCOMLazyGetter(JSContext* aCx,
+                                      JSObject* aObj,
+                                      jsid aId,
+                                      jsval* aVp)
+{
+#ifdef DEBUG
+  {
+    NS_ASSERTION(JS_GetGlobalForObject(aCx, aObj) == aObj, "Bad object!");
+    NS_ASSERTION(JSID_IS_STRING(aId), "Not a string!");
+    NS_ASSERTION(nsDependentJSString(aId).EqualsLiteral("XPCOM"), "Bad id!");
+  }
+#endif
+  nsDOMWorker* worker = static_cast<nsDOMWorker*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  if (worker->IsCanceled()) {
+    return JS_FALSE;
+  }
+
+  PRUint16 dummy;
+  nsCOMPtr<nsIXPCSecurityManager> secMan;
+  nsContentUtils::XPConnect()->
+    GetSecurityManagerForJSContext(aCx, getter_AddRefs(secMan), &dummy);
+  if (!secMan) {
+    JS_ReportError(aCx, "Could not get security manager!");
+    return JS_FALSE;
+  }
+
+  nsCID dummyCID;
+  if (NS_FAILED(secMan->CanGetService(aCx, dummyCID))) {
+    JS_ReportError(aCx, "Access to the XPCOM object is denied!");
+    return JS_FALSE;
+  }
+
+  JSObject* xpcom = JS_NewObject(aCx, nsnull, nsnull, nsnull);
+  NS_ENSURE_TRUE(xpcom, JS_FALSE);
+
+  JSBool ok = JS_DefineFunctions(aCx, xpcom, gDOMWorkerXPCOMFunctions);
+  NS_ENSURE_TRUE(ok, JS_FALSE);
+
+  ok = JS_DeletePropertyById(aCx, aObj, aId);
+  NS_ENSURE_TRUE(ok, JS_FALSE);
+
+  jsval xpcomVal = OBJECT_TO_JSVAL(xpcom);
+  ok = JS_SetPropertyById(aCx, aObj, aId, &xpcomVal);
+  NS_ENSURE_TRUE(ok, JS_FALSE);
+
+  JS_SET_RVAL(aCx, aVp, xpcomVal);
+  return JS_TRUE;
+}
+
+JSBool
+nsDOMWorkerFunctions::GetInstanceCommon(JSContext* aCx,
+                                        uintN aArgc,
+                                        jsval* aVp,
+                                        PRBool aService)
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsDOMWorker* worker = static_cast<nsDOMWorker*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  if (worker->IsCanceled()) {
+    return JS_FALSE;
+  }
+
+  if (!aArgc) {
+    JS_ReportError(aCx, "Function requires at least 1 parameter");
+    return JS_FALSE;
+  }
+
+  JSString* str = JS_ValueToString(aCx, JS_ARGV(aCx, aVp)[0]);
+  if (!str) {
+    NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!");
+    return JS_FALSE;
+  }
+
+  JSAutoByteString strBytes(aCx, str);
+  if (!strBytes) {
+    NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!");
+    return JS_FALSE;
+  }
+
+  nsDependentCString contractId(strBytes.ptr(), JS_GetStringLength(str));
+
+  nsDOMThreadService* threadService = nsDOMThreadService::get();
+
+  ThreadsafeStatus status =
+    threadService->GetContractIdThreadsafeStatus(contractId);
+
+  if (status == Unknown) {
+    nsCOMPtr<nsIThread> mainThread;
+    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+    if (NS_FAILED(rv)) {
+      JS_ReportError(aCx, "Failed to get main thread!");
+      return JS_FALSE;
+    }
+
+    nsRefPtr<TestComponentThreadsafetyRunnable> runnable =
+      new TestComponentThreadsafetyRunnable(contractId, aService);
+
+    rv = mainThread->Dispatch(runnable, NS_DISPATCH_SYNC);
+    if (NS_FAILED(rv)) {
+      JS_ReportError(aCx, "Failed to check threadsafety!");
+      return JS_FALSE;
+    }
+
+    // The worker may have been canceled while waiting above. Check again.
+    if (worker->IsCanceled()) {
+      return JS_FALSE;
+    }
+
+    if (runnable->IsThreadsafe()) {
+      threadService->NoteThreadsafeContractId(contractId, PR_TRUE);
+      status = Threadsafe;
+    }
+    else {
+      threadService->NoteThreadsafeContractId(contractId, PR_FALSE);
+      status = NotThreadsafe;
+    }
+  }
+
+  if (status == NotThreadsafe) {
+    JS_ReportError(aCx, "ChromeWorker may not create an XPCOM object that is "
+                   "not threadsafe!");
+    return JS_FALSE;
+  }
+
+  nsCOMPtr<nsISupports> instance;
+  if (aService) {
+    instance = do_GetService(contractId.get());
+    if (!instance) {
+      JS_ReportError(aCx, "Could not get the service!");
+      return JS_FALSE;
+    }
+  }
+  else {
+    instance = do_CreateInstance(contractId.get());
+    if (!instance) {
+      JS_ReportError(aCx, "Could not create the instance!");
+      return JS_FALSE;
+    }
+  }
+
+  JSObject* global = JS_GetGlobalForObject(aCx, JS_GetScopeChain(aCx));
+  if (!global) {
+    NS_ASSERTION(JS_IsExceptionPending(aCx), "Need to set an exception!");
+    return JS_FALSE;
+  }
+
+  jsval val;
+  nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
+  if (NS_FAILED(nsContentUtils::WrapNative(aCx, global, instance, &val,
+                                           getter_AddRefs(wrapper)))) {
+    JS_ReportError(aCx, "Failed to wrap object!");
+    return JS_FALSE;
+  }
+
+  JS_SET_RVAL(aCx, aVp, val);
+  return JS_TRUE;
+}
+
+JSBool
 nsDOMWorkerFunctions::MakeNewWorker(JSContext* aCx,
                                     uintN aArgc,
                                     jsval* aVp,
                                     WorkerPrivilegeModel aPrivilegeModel)
 {
   JSObject *obj = JS_THIS_OBJECT(aCx, aVp);
   if (!obj) {
     return JS_FALSE;
@@ -550,16 +787,81 @@ JSFunctionSpec gDOMWorkerFunctions[] = {
   { nsnull,                nsnull,                                    0, 0 }
 };
 
 JSFunctionSpec gDOMWorkerChromeFunctions[] = {
   { "ChromeWorker",        nsDOMWorkerFunctions::NewChromeWorker,     1, 0 },
   { nsnull,                nsnull,                                    0, 0 }
 };
 
+
+enum DOMWorkerStructuredDataType
+{
+  // We have a special tag for XPCWrappedNatives that are being passed between
+  // threads. This will not work across processes and cannot be persisted. Only
+  // for ChromeWorker use at present.
+  DOMWORKER_SCTAG_WRAPPEDNATIVE = JS_SCTAG_USER_MIN + 0x1000,
+
+  DOMWORKER_SCTAG_END
+};
+
+PR_STATIC_ASSERT(DOMWORKER_SCTAG_END <= JS_SCTAG_USER_MAX);
+
+// static
+JSBool
+WriteStructuredClone(JSContext* aCx,
+                     JSStructuredCloneWriter* aWriter,
+                     JSObject* aObj,
+                     void* aClosure)
+{
+  NS_ASSERTION(aClosure, "Null pointer!");
+
+  // We'll stash any nsISupports pointers that need to be AddRef'd here.
+  nsTArray<nsCOMPtr<nsISupports> >* wrappedNatives =
+    static_cast<nsTArray<nsCOMPtr<nsISupports> >*>(aClosure);
+
+  // See if this is a wrapped native.
+  nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative;
+  nsContentUtils::XPConnect()->
+    GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative));
+  if (wrappedNative) {
+    // Get the raw nsISupports out of it.
+    nsISupports* wrappedObject = wrappedNative->Native();
+    NS_ASSERTION(wrappedObject, "Null pointer?!");
+
+    // See if this nsISupports is threadsafe.
+    nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(wrappedObject);
+    if (classInfo) {
+      PRUint32 flags;
+      if (NS_SUCCEEDED(classInfo->GetFlags(&flags)) &&
+          (flags & nsIClassInfo::THREADSAFE)) {
+        // Write the raw pointer into the stream, and add it to the list we're
+        // building.
+        return JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_WRAPPEDNATIVE, 0) &&
+               JS_WriteBytes(aWriter, &wrappedObject, sizeof(wrappedObject)) &&
+               wrappedNatives->AppendElement(wrappedObject);
+      }
+    }
+  }
+
+  // Something failed above, try using the runtime callbacks instead.
+  const JSStructuredCloneCallbacks* runtimeCallbacks =
+    aCx->runtime->structuredCloneCallbacks;
+  if (runtimeCallbacks) {
+    return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull);
+  }
+
+  // We can't handle this object, throw an exception if one hasn't been thrown
+  // already.
+  if (!JS_IsExceptionPending(aCx)) {
+    nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+  }
+  return JS_FALSE;
+}
+
 nsDOMWorkerScope::nsDOMWorkerScope(nsDOMWorker* aWorker)
 : mWorker(aWorker),
   mWrappedNative(nsnull),
   mHasOnerror(PR_FALSE)
 {
   NS_ASSERTION(aWorker, "Null pointer!");
 }
 
@@ -1661,33 +1963,41 @@ nsDOMWorker::PostMessageInternal(PRBool 
   jsval* argv;
   rv = cc->GetArgvPtr(&argv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   JSContext* cx;
   rv = cc->GetJSContext(&cx);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // If we're a ChromeWorker then we allow wrapped natives to be passed via
+  // structured cloning by supplying a custom write callback. To do that we need
+  // to make sure they stay alive while the message is being sent, so we collect
+  // the wrapped natives in an array to be packaged with the message.
+  JSStructuredCloneCallbacks callbacks = {
+    nsnull, IsPrivileged() ? WriteStructuredClone : nsnull, nsnull
+  };
+
   JSAutoRequest ar(cx);
 
   JSAutoStructuredCloneBuffer buffer;
-
-  if (!buffer.write(cx, argv[0])) {
+  nsTArray<nsCOMPtr<nsISupports> > wrappedNatives;
+  if (!buffer.write(cx, argv[0], &callbacks, &wrappedNatives)) {
     return NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
   nsRefPtr<nsDOMWorkerMessageEvent> message = new nsDOMWorkerMessageEvent();
   NS_ENSURE_TRUE(message, NS_ERROR_OUT_OF_MEMORY);
 
   rv = message->InitMessageEvent(NS_LITERAL_STRING("message"), PR_FALSE,
                                  PR_FALSE, EmptyString(), EmptyString(),
                                  nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = message->SetJSData(cx, buffer);
+  rv = message->SetJSData(cx, buffer, wrappedNatives);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<nsDOMFireEventRunnable> runnable =
     new nsDOMFireEventRunnable(this, message, aToInner);
   NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY);
 
   // If aToInner is true then we want to target the runnable at this worker's
   // thread. Otherwise we need to target the parent's thread.
@@ -1801,16 +2111,21 @@ nsDOMWorker::CompileGlobalObject(JSConte
   success = JS_DefineFunctions(aCx, global, gDOMWorkerFunctions);
   NS_ENSURE_TRUE(success, PR_FALSE);
 
   if (IsPrivileged()) {
     // Add chrome functions.
     success = JS_DefineFunctions(aCx, global, gDOMWorkerChromeFunctions);
     NS_ENSURE_TRUE(success, PR_FALSE);
 
+    success = JS_DefineProperty(aCx, global, "XPCOM", JSVAL_VOID,
+                                nsDOMWorkerFunctions::XPCOMLazyGetter, nsnull,
+                                0);
+    NS_ENSURE_TRUE(success, PR_FALSE);
+
 #ifdef BUILD_CTYPES
     // Add the lazy getter for ctypes.
     success = JS_DefineProperty(aCx, global, "ctypes", JSVAL_VOID,
                                 nsDOMWorkerFunctions::CTypesLazyGetter, nsnull,
                                 0);
     NS_ENSURE_TRUE(success, PR_FALSE);
 #endif
   }
@@ -2197,16 +2512,63 @@ nsDOMWorker::SetExpirationTime(PRInterva
 PRIntervalTime
 nsDOMWorker::GetExpirationTime()
 {
   nsAutoLock lock(mLock);
   return mExpirationTime;
 }
 #endif
 
+// static
+JSObject*
+nsDOMWorker::ReadStructuredClone(JSContext* aCx,
+                                 JSStructuredCloneReader* aReader,
+                                 uint32 aTag,
+                                 uint32 aData,
+                                 void* aClosure)
+{
+  NS_ASSERTION(aCx, "Null context!");
+  NS_ASSERTION(aReader, "Null reader!");
+  NS_ASSERTION(!aClosure, "Shouldn't have a closure here!");
+
+  if (aTag == DOMWORKER_SCTAG_WRAPPEDNATIVE) {
+    NS_ASSERTION(!aData, "Huh?");
+
+    nsISupports* wrappedNative;
+    if (JS_ReadBytes(aReader, &wrappedNative, sizeof(wrappedNative))) {
+      NS_ASSERTION(wrappedNative, "Null pointer?!");
+
+      JSObject* global = JS_GetGlobalForObject(aCx, JS_GetScopeChain(aCx));
+      if (global) {
+        jsval val;
+        nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
+        if (NS_SUCCEEDED(nsContentUtils::WrapNative(aCx, global, wrappedNative,
+                                                    &val,
+                                                    getter_AddRefs(wrapper)))) {
+          return JSVAL_TO_OBJECT(val);
+        }
+      }
+    }
+  }
+
+  // Something failed above, try using the runtime callbacks instead.
+  const JSStructuredCloneCallbacks* runtimeCallbacks =
+    aCx->runtime->structuredCloneCallbacks;
+  if (runtimeCallbacks) {
+    return runtimeCallbacks->read(aCx, aReader, aTag, aData, nsnull);
+  }
+
+  // We can't handle this object, throw an exception if one hasn't been thrown
+  // already.
+  if (!JS_IsExceptionPending(aCx)) {
+    nsDOMClassInfo::ThrowJSException(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+  }
+  return nsnull;
+}
+
 PRBool
 nsDOMWorker::QueueSuspendedRunnable(nsIRunnable* aRunnable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   return mQueuedRunnables.AppendElement(aRunnable) ? PR_TRUE : PR_FALSE;
 }
 
 NS_IMETHODIMP
--- a/dom/src/threads/nsDOMWorker.h
+++ b/dom/src/threads/nsDOMWorker.h
@@ -229,16 +229,22 @@ public:
 #ifdef DEBUG
   PRIntervalTime GetExpirationTime();
 #endif
 
   PRBool IsPrivileged() {
     return mPrivilegeModel == CHROME;
   }
 
+  static JSObject* ReadStructuredClone(JSContext* aCx,
+                                       JSStructuredCloneReader* aReader,
+                                       uint32 aTag,
+                                       uint32 aData,
+                                       void* aClosure);
+
   /**
    * Use this chart to help figure out behavior during each of the closing
    * statuses. Details below.
    * 
    * +=============+=============+=================+=======================+
    * |   status    | clear queue | abort execution | close handler timeout |
    * +=============+=============+=================+=======================+
    * |   eClosed   |     yes     |       no        |          no           |
--- a/dom/src/threads/nsDOMWorkerEvents.cpp
+++ b/dom/src/threads/nsDOMWorkerEvents.cpp
@@ -274,26 +274,32 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsDOMWorker
                                                       nsIWorkerMessageEvent)
 
 NS_IMPL_CI_INTERFACE_GETTER2(nsDOMWorkerMessageEvent, nsIDOMEvent,
                                                       nsIWorkerMessageEvent)
 
 NS_IMPL_THREADSAFE_DOM_CI_GETINTERFACES(nsDOMWorkerMessageEvent)
 
 nsresult
-nsDOMWorkerMessageEvent::SetJSData(JSContext* aCx,
-                                   JSAutoStructuredCloneBuffer& aBuffer)
+nsDOMWorkerMessageEvent::SetJSData(
+                              JSContext* aCx,
+                              JSAutoStructuredCloneBuffer& aBuffer,
+                              nsTArray<nsCOMPtr<nsISupports> >& aWrappedNatives)
 {
   NS_ASSERTION(aCx, "Null context!");
 
   if (!mDataVal.Hold(aCx)) {
     NS_WARNING("Failed to hold jsval!");
     return NS_ERROR_FAILURE;
   }
 
+  if (!mWrappedNatives.SwapElements(aWrappedNatives)) {
+    NS_ERROR("This should never fail!");
+  }
+
   aBuffer.steal(&mData, &mDataLen);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerMessageEvent::GetData(nsAString& aData)
 {
   nsIXPConnect* xpc = nsContentUtils::XPConnect();
@@ -310,17 +316,27 @@ nsDOMWorkerMessageEvent::GetData(nsAStri
     NS_ENSURE_SUCCESS(rv, rv);
 
     JSAutoRequest ar(cx);
     JSAutoStructuredCloneBuffer buffer;
     buffer.adopt(cx, mData, mDataLen);
     mData = nsnull;
     mDataLen = 0;
 
-    if (!buffer.read(mDataVal.ToJSValPtr())) {
+    JSStructuredCloneCallbacks callbacks = {
+      nsDOMWorker::ReadStructuredClone, nsnull, nsnull
+    };
+
+    JSBool ok = buffer.read(mDataVal.ToJSValPtr(), cx, &callbacks);
+
+    // Release wrapped natives now, regardless of whether or not the deserialize
+    // succeeded.
+    mWrappedNatives.Clear();
+
+    if (!ok) {
       NS_WARNING("Failed to deserialize!");
       return NS_ERROR_FAILURE;
     }
   }
 
   jsval* retval;
   rv = cc->GetRetValPtr(&retval);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/src/threads/nsDOMWorkerEvents.h
+++ b/dom/src/threads/nsDOMWorkerEvents.h
@@ -210,25 +210,27 @@ public:
   NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::)
   NS_DECL_NSIWORKERMESSAGEEVENT
   NS_DECL_NSICLASSINFO_GETINTERFACES
 
   nsDOMWorkerMessageEvent() : mData(nsnull) { }
   ~nsDOMWorkerMessageEvent();
 
   nsresult SetJSData(JSContext* aCx,
-                     JSAutoStructuredCloneBuffer& aBuffer);
+                     JSAutoStructuredCloneBuffer& aBuffer,
+                     nsTArray<nsCOMPtr<nsISupports> >& aWrappedNatives);
 
 protected:
   nsString mOrigin;
   nsCOMPtr<nsISupports> mSource;
 
   nsAutoJSValHolder mDataVal;
   uint64* mData;
   size_t mDataLen;
+  nsTArray<nsCOMPtr<nsISupports> > mWrappedNatives;
 };
 
 class nsDOMWorkerProgressEvent : public nsDOMWorkerEvent,
                                  public nsIDOMProgressEvent
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_NSIDOMEVENT(nsDOMWorkerEvent::)
--- a/dom/src/threads/nsDOMWorkerSecurityManager.cpp
+++ b/dom/src/threads/nsDOMWorkerSecurityManager.cpp
@@ -38,19 +38,22 @@
 
 #include "nsDOMWorkerSecurityManager.h"
 
 // Interfaces
 #include "nsIClassInfo.h"
 
 // Other includes
 #include "jsapi.h"
+#include "nsDOMError.h"
+#include "nsThreadUtils.h"
 
 // DOMWorker includes
 #include "nsDOMThreadService.h"
+#include "nsDOMWorker.h"
 
 #define LOG(_args) PR_LOG(gDOMThreadsLog, PR_LOG_DEBUG, _args)
 
 class nsDOMWorkerPrincipal
 {
 public:
   static void Destroy(JSContext*, JSPrincipals*) {
     // nothing
@@ -68,39 +71,42 @@ static JSPrincipals gWorkerPrincipal =
   1 /* refcount */,
   nsDOMWorkerPrincipal::Destroy /* destroy */,
   nsDOMWorkerPrincipal::Subsume /* subsume */ };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsDOMWorkerSecurityManager,
                               nsIXPCSecurityManager)
 
 NS_IMETHODIMP
-nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aJSContext,
+nsDOMWorkerSecurityManager::CanCreateWrapper(JSContext* aCx,
                                              const nsIID& aIID,
                                              nsISupports* aObj,
                                              nsIClassInfo* aClassInfo,
                                              void** aPolicy)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aJSContext,
+nsDOMWorkerSecurityManager::CanCreateInstance(JSContext* aCx,
                                               const nsCID& aCID)
 {
-  NS_NOTREACHED("Should not call this!");
-  return NS_ERROR_UNEXPECTED;
+  return CanGetService(aCx, aCID);
 }
 
 NS_IMETHODIMP
-nsDOMWorkerSecurityManager::CanGetService(JSContext* aJSContext,
+nsDOMWorkerSecurityManager::CanGetService(JSContext* aCx,
                                           const nsCID& aCID)
 {
-  NS_NOTREACHED("Should not call this!");
-  return NS_ERROR_UNEXPECTED;
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsDOMWorker* worker = static_cast<nsDOMWorker*>(JS_GetContextPrivate(aCx));
+  NS_ASSERTION(worker, "This should be set by the DOM thread service!");
+
+  return worker->IsPrivileged() ? NS_OK : NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED;
 }
 
 NS_IMETHODIMP
 nsDOMWorkerSecurityManager::CanAccess(PRUint32 aAction,
                                       nsAXPCNativeCallContext* aCallContext,
                                       JSContext* aJSContext,
                                       JSObject* aJSObject,
                                       nsISupports* aObj,
--- a/dom/src/threads/test/Makefile.in
+++ b/dom/src/threads/test/Makefile.in
@@ -113,16 +113,18 @@ include $(topsrcdir)/config/rules.mk
   xhrAbort_worker.js \
   testXHR.txt \
   test_fibonacci.html \
   fibonacci_worker.js \
   test_newError.html \
   newError_worker.js \
   test_chromeWorker.html \
   WorkerTest_badworker.js \
+  test_xpcom.html \
+  xpcom_worker.js \
   $(NULL)
 
 _SUBDIR_TEST_FILES = \
   relativeLoad_sub_worker.js \
   relativeLoad_sub_worker2.js \
   relativeLoad_sub_import.js \
   $(NULL)
 
--- a/dom/src/threads/test/chromeWorker_worker.js
+++ b/dom/src/threads/test/chromeWorker_worker.js
@@ -30,13 +30,39 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-let worker = new ChromeWorker("chromeWorker_subworker.js");
-worker.onmessage = function(event) {
-  postMessage(event.data);
+// Test XPCOM.getService()
+let threadMan = XPCOM.getService("@mozilla.org/thread-manager;1");
+let mainThread = threadMan.mainThread;
+if (mainThread.isOnCurrentThread()) {
+  throw "Thread manager is lying to us!";
+}
+
+// Test XPCOM.createInstance
+let threadPool = XPCOM.createInstance("@mozilla.org/thread-pool;1");
+threadPool.shutdown();
+
+let notThreadsafe;
+try {
+  notThreadsafe = XPCOM.createInstance("@mozilla.org/supports-PRBool;1");
 }
-worker.postMessage("Go");
+catch(e) { }
+
+if (notThreadsafe) {
+  throw "Shouldn't be able to create non-threadsafe component!";
+}
+
+function onmessage(event) {
+  // Test passing wrapped natives from the main thread.
+  event.data.shutdown();
+
+  let worker = new ChromeWorker("chromeWorker_subworker.js");
+  worker.onmessage = function(event) {
+    postMessage(event.data);
+  }
+  worker.postMessage("Go");
+}
--- a/dom/src/threads/test/test_chromeWorker.xul
+++ b/dom/src/threads/test/test_chromeWorker.xul
@@ -59,16 +59,35 @@
         is(event.data, "Done!", "Wrong message!");
         SimpleTest.finish();
       }
       worker.onerror = function(event) {
         ok(false, "Worker had an error: " + event.message);
         worker.terminate();
         SimpleTest.finish();
       }
+
+      // Test passing a non-threadsafe wrapped native to the worker.
+      var isupports =
+        Components.classes["@mozilla.org/supports-PRBool;1"]
+                  .createInstance(Components.interfaces.nsISupportsPRBool);
+
+      try {
+        worker.postMessage(isupports);
+        ok(false, "Passing non-threadsafe thing should throw!");
+      }
+      catch (e) {
+        ok(true, "Passing non-threadsafe thing threw");
+      }
+
+      // Test passing a wrapped native to the worker.
+      var thread = Components.classes["@mozilla.org/thread-manager;1"]
+                             .getService().newThread(0);
+
+      worker.postMessage(thread);
     }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
     <div id="content" style="display:none;"></div>
--- a/dom/src/threads/test/test_chromeWorkerComponent.xul
+++ b/dom/src/threads/test/test_chromeWorkerComponent.xul
@@ -63,16 +63,22 @@
         is(event.data, "Done!", "Wrong message!");
         SimpleTest.finish();
       }
       worker.onerror = function(event) {
         ok(false, "Worker had an error: " + event.message);
         worker.terminate();
         SimpleTest.finish();
       }
+
+      // Test passing a wrapped native to the worker.
+      var thread = Components.classes["@mozilla.org/thread-manager;1"]
+                             .getService().newThread(0);
+
+      worker.postMessage(thread);
     }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
     <div id="content" style="display:none;"></div>
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/test_xpcom.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for DOM Worker Threads</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  var worker = new Worker("xpcom_worker.js");
+  worker.onmessage = function(event) {
+    is(event.data, "Done", "Correct message");
+    SimpleTest.finish();
+  };
+  worker.onerror = function(event) {
+    ok(false, "Worker had an error: " + event.message);
+    SimpleTest.finish();
+  };
+
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  // Test passing a wrapped native to the worker.
+  var thread = Components.classes["@mozilla.org/thread-manager;1"]
+                         .getService().newThread(0);
+
+  try {
+    worker.postMessage(thread);
+    ok(false, "postMessage with a wrapped native should fail!");
+  }
+  catch(e) {
+    ok(true, "postMessage with a wrapped native failed");
+  }
+
+  worker.postMessage("Hi");
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/src/threads/test/xpcom_worker.js
@@ -0,0 +1,18 @@
+var exception;
+try {
+  var xpcom = XPCOM;
+}
+catch(e) {
+  exception = e;
+}
+
+if (!exception) {
+  throw "Worker shouldn't be able to access the XPCOM object!";
+}
+
+onmessage = function(event) {
+  if (event.data != "Hi") {
+    throw "Bad message!";
+  }
+  postMessage("Done");
+}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5557,36 +5557,58 @@ JS_PUBLIC_API(JSBool)
 JS_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, reviver);
     return js_FinishJSONParse(cx, jp, Valueify(reviver));
 }
 
 JS_PUBLIC_API(JSBool)
-JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes, uint32 version, jsval *vp)
+JS_ReadStructuredClone(JSContext *cx, const uint64 *buf, size_t nbytes,
+                       uint32 version, jsval *vp,
+                       const JSStructuredCloneCallbacks *optionalCallbacks,
+                       void *closure)
 {
     if (version > JS_STRUCTURED_CLONE_VERSION) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_CLONE_VERSION);
         return false;
     }
-    return ReadStructuredClone(cx, buf, nbytes, Valueify(vp));
+    const JSStructuredCloneCallbacks *callbacks =
+        optionalCallbacks ?
+        optionalCallbacks :
+        cx->runtime->structuredCloneCallbacks;
+    return ReadStructuredClone(cx, buf, nbytes, Valueify(vp), callbacks, closure);
 }
 
 JS_PUBLIC_API(JSBool)
-JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp)
-{
-    return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp);
+JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **bufp, size_t *nbytesp,
+                        const JSStructuredCloneCallbacks *optionalCallbacks,
+                        void *closure)
+{
+    const JSStructuredCloneCallbacks *callbacks =
+        optionalCallbacks ?
+        optionalCallbacks :
+        cx->runtime->structuredCloneCallbacks;
+    return WriteStructuredClone(cx, Valueify(v), (uint64_t **) bufp, nbytesp,
+                                callbacks, closure);
 }
 
 JS_PUBLIC_API(JSBool)
-JS_StructuredClone(JSContext *cx, jsval v, jsval *vp)
-{
+JS_StructuredClone(JSContext *cx, jsval v, jsval *vp,
+                   ReadStructuredCloneOp optionalReadOp,
+                   const JSStructuredCloneCallbacks *optionalCallbacks,
+                   void *closure)
+{
+    const JSStructuredCloneCallbacks *callbacks =
+        optionalCallbacks ?
+        optionalCallbacks :
+        cx->runtime->structuredCloneCallbacks;
     JSAutoStructuredCloneBuffer buf;
-    return buf.write(cx, v) && buf.read(vp);
+    return buf.write(cx, v, callbacks, closure) &&
+           buf.read(vp, cx, callbacks, closure);
 }
 
 JS_PUBLIC_API(void)
 JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks)
 {
     rt->structuredCloneCallbacks = callbacks;
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3280,25 +3280,38 @@ JS_FinishJSONParse(JSContext *cx, JSONPa
 
 /************************************************************************/
 
 /* API for the HTML5 internal structured cloning algorithm. */
 
 /* The maximum supported structured-clone serialization format version. */
 #define JS_STRUCTURED_CLONE_VERSION 1
 
+struct JSStructuredCloneCallbacks {
+    ReadStructuredCloneOp read;
+    WriteStructuredCloneOp write;
+    StructuredCloneErrorOp reportError;
+};
+
 JS_PUBLIC_API(JSBool)
-JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes, uint32 version, jsval *vp);
+JS_ReadStructuredClone(JSContext *cx, const uint64 *data, size_t nbytes,
+                       uint32 version, jsval *vp,
+                       const JSStructuredCloneCallbacks *optionalCallbacks,
+                       void *closure);
 
 /* Note: On success, the caller is responsible for calling js_free(*datap). */
 JS_PUBLIC_API(JSBool)
-JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp);
+JS_WriteStructuredClone(JSContext *cx, jsval v, uint64 **datap, size_t *nbytesp,
+                        const JSStructuredCloneCallbacks *optionalCallbacks,
+                        void *closure);
 
 JS_PUBLIC_API(JSBool)
-JS_StructuredClone(JSContext *cx, jsval v, jsval *vp);
+JS_StructuredClone(JSContext *cx, jsval v, jsval *vp,
+                   const JSStructuredCloneCallbacks *optionalCallbacks,
+                   void *closure);
 
 #ifdef __cplusplus
 /* RAII sugar for JS_WriteStructuredClone. */
 class JSAutoStructuredCloneBuffer {
     JSContext *cx_;
     uint64 *data_;
     size_t nbytes_;
     uint32 version_;
@@ -3353,28 +3366,34 @@ class JSAutoStructuredCloneBuffer {
             *versionp = version_;
 
         cx_ = NULL;
         data_ = NULL;
         nbytes_ = 0;
         version_ = 0;
     }
 
-    bool read(jsval *vp, JSContext *cx=NULL) const {
+    bool read(jsval *vp, JSContext *cx=NULL,
+              const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
+              void *closure=NULL) const {
         if (!cx)
             cx = cx_;
         JS_ASSERT(cx);
         JS_ASSERT(data_);
-        return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp);
+        return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp,
+                                        optionalCallbacks, closure);
     }
 
-    bool write(JSContext *cx, jsval v) {
+    bool write(JSContext *cx, jsval v,
+               const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
+               void *closure=NULL) {
         clear(cx);
         cx_ = cx;
-        bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_);
+        bool ok = !!JS_WriteStructuredClone(cx, v, &data_, &nbytes_,
+                                            optionalCallbacks, closure);
         if (!ok) {
             data_ = NULL;
             nbytes_ = 0;
             version_ = JS_STRUCTURED_CLONE_VERSION;
         }
         return ok;
     }
 
@@ -3408,22 +3427,16 @@ class JSAutoStructuredCloneBuffer {
 /* API for implementing custom serialization behavior (for ImageData, File, etc.) */
 
 /* The range of tag values the application may use for its own custom object types. */
 #define JS_SCTAG_USER_MIN  ((uint32) 0xFFFF8000)
 #define JS_SCTAG_USER_MAX  ((uint32) 0xFFFFFFFF)
 
 #define JS_SCERR_RECURSION 0
 
-struct JSStructuredCloneCallbacks {
-    ReadStructuredCloneOp read;
-    WriteStructuredCloneOp write;
-    StructuredCloneErrorOp reportError;
-};
-
 JS_PUBLIC_API(void)
 JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks);
 
 JS_PUBLIC_API(JSBool)
 JS_ReadUint32Pair(JSStructuredCloneReader *r, uint32 *p1, uint32 *p2);
 
 JS_PUBLIC_API(JSBool)
 JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len);
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -44,28 +44,30 @@
 #include "jsregexpinlines.h"
 
 using namespace js;
 
 namespace js
 {
 
 bool
-WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp)
+WriteStructuredClone(JSContext *cx, const Value &v, uint64 **bufp, size_t *nbytesp,
+                     const JSStructuredCloneCallbacks *cb, void *cbClosure)
 {
     SCOutput out(cx);
-    JSStructuredCloneWriter w(out);
+    JSStructuredCloneWriter w(out, cb, cbClosure);
     return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
 }
 
 bool
-ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp)
+ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
+                    const JSStructuredCloneCallbacks *cb, void *cbClosure)
 {
     SCInput in(cx, data, nbytes);
-    JSStructuredCloneReader r(in);
+    JSStructuredCloneReader r(in, cb, cbClosure);
     return r.read(vp);
 }
 
 }
 
 enum StructuredDataType {
     /* Structured data types provided by the engine */
     SCTAG_FLOAT_MAX = 0xFFF00000,
@@ -454,19 +456,18 @@ bool
 JSStructuredCloneWriter::startObject(JSObject *obj)
 {
     JS_ASSERT(obj->isArray() || obj->isObject());
 
     /* Fail if obj is already on the stack. */
     HashSet<JSObject *>::AddPtr p = memory.lookupForAdd(obj);
     if (p) {
         JSContext *cx = context();
-        const JSStructuredCloneCallbacks *cb = cx->runtime->structuredCloneCallbacks;
-        if (cb)
-            cb->reportError(cx, JS_SCERR_RECURSION);
+        if (callbacks && callbacks->reportError)
+            callbacks->reportError(cx, JS_SCERR_RECURSION);
         else
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_RECURSION);
         return false;
     }
     if (!memory.add(p, obj))
         return false;
 
     /*
@@ -521,19 +522,18 @@ JSStructuredCloneWriter::startWrite(cons
             return out.writePair(SCTAG_BOOLEAN_OBJECT, obj->getPrimitiveThis().toBoolean());
         } else if (obj->isNumber()) {
             return out.writePair(SCTAG_NUMBER_OBJECT, 0) &&
                    out.writeDouble(obj->getPrimitiveThis().toNumber());
         } else if (obj->isString()) {
             return writeString(SCTAG_STRING_OBJECT, obj->getPrimitiveThis().toString());
         }
 
-        const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks;
-        if (cb)
-            return cb->write(context(), this, obj);
+        if (callbacks && callbacks->write)
+            return callbacks->write(context(), this, obj, closure);
         /* else fall through */
     }
 
     JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
     return false;
 }
 
 bool
@@ -778,23 +778,22 @@ JSStructuredCloneReader::startRead(Value
                 return false;
             vp->setNumber(d);
             break;
         }
 
         if (SCTAG_TYPED_ARRAY_MIN <= tag && tag <= SCTAG_TYPED_ARRAY_MAX)
             return readTypedArray(tag, data, vp);
 
-        const JSStructuredCloneCallbacks *cb = context()->runtime->structuredCloneCallbacks;
-        if (!cb) {
+        if (!callbacks || !callbacks->read) {
             JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA,
                                  "unsupported type");
             return false;
         }
-        JSObject *obj = cb->read(context(), this, tag, data);
+        JSObject *obj = callbacks->read(context(), this, tag, data, closure);
         if (!obj)
             return false;
         vp->setObject(*obj);
       }
     }
     return true;
 }
 
--- a/js/src/jsclone.h
+++ b/js/src/jsclone.h
@@ -44,20 +44,22 @@
 #include "jshashtable.h"
 #include "jsstdint.h"
 #include "jsvector.h"
 #include "jsvalue.h"
 
 namespace js {
 
 bool
-WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp);
+WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
+                     const JSStructuredCloneCallbacks *cb, void *cbClosure);
 
 bool
-ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp);
+ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
+                    const JSStructuredCloneCallbacks *cb, void *cbClosure);
 
 struct SCOutput {
   public:
     explicit SCOutput(JSContext *cx);
 
     JSContext *context() const { return cx; }
 
     bool write(uint64_t u);
@@ -104,18 +106,19 @@ struct SCInput {
     const uint64_t *point;
     const uint64_t *end;
 };
 
 }
 
 struct JSStructuredCloneReader {
   public:
-    explicit JSStructuredCloneReader(js::SCInput &in)
-        : in(in), objs(in.context()) {}
+    explicit JSStructuredCloneReader(js::SCInput &in, const JSStructuredCloneCallbacks *cb,
+                                     void *cbClosure)
+        : in(in), objs(in.context()), callbacks(cb), closure(cbClosure) { }
 
     js::SCInput &input() { return in; }
     bool read(js::Value *vp);
 
   private:
     JSContext *context() { return in.context(); }
 
     bool checkDouble(jsdouble d);
@@ -124,23 +127,30 @@ struct JSStructuredCloneReader {
     bool readArrayBuffer(uint32_t nbytes, js::Value *vp);
     bool readId(jsid *idp);
     bool startRead(js::Value *vp);
 
     js::SCInput &in;
 
     // Stack of objects with properties remaining to be read.
     js::AutoValueVector objs;
+
+    // The user defined callbacks that will be used for cloning.
+    const JSStructuredCloneCallbacks *callbacks;
+
+    // Any value passed to JS_ReadStructuredClone.
+    void *closure;
 };
 
 struct JSStructuredCloneWriter {
   public:
-    explicit JSStructuredCloneWriter(js::SCOutput &out)
+    explicit JSStructuredCloneWriter(js::SCOutput &out, const JSStructuredCloneCallbacks *cb,
+                                     void *cbClosure)
         : out(out), objs(out.context()), counts(out.context()), ids(out.context()),
-          memory(out.context()) {}
+          memory(out.context()), callbacks(cb), closure(cbClosure) { }
 
     bool init() { return memory.init(); }
 
     bool write(const js::Value &v);
 
     js::SCOutput &output() { return out; }
 
   private:
@@ -165,11 +175,17 @@ struct JSStructuredCloneWriter {
     js::Vector<size_t> counts;
 
     // Ids of properties remaining to be written.
     js::AutoIdVector ids;
 
     // The "memory" list described in the HTML5 internal structured cloning algorithm.
     // memory has the same elements as objs.
     js::HashSet<JSObject *> memory;
+
+    // The user defined callbacks that will be used for cloning.
+    const JSStructuredCloneCallbacks *callbacks;
+
+    // Any value passed to JS_WriteStructuredClone.
+    void *closure;
 };
 
 #endif /* jsclone_h___ */
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -579,33 +579,35 @@ typedef JSBool
 (* JSCompartmentCallback)(JSContext *cx, JSCompartment *compartment, uintN compartmentOp);
 
 /*
  * Read structured data from the reader r. This hook is used to read a value
  * previously serialized by a call to the WriteStructuredCloneOp hook.
  *
  * tag and data are the pair of uint32 values from the header. The callback may
  * use the JS_Read* APIs to read any other relevant parts of the object from
- * the reader r. Return the new object on success, NULL on error/exception.
+ * the reader r. closure is any value passed to the JS_ReadStructuredClone
+ * function. Return the new object on success, NULL on error/exception.
  */
 typedef JSObject *(*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReader *r,
-                                           uint32 tag, uint32 data);
+                                           uint32 tag, uint32 data, void *closure);
 
 /*
  * Structured data serialization hook. The engine can write primitive values,
  * Objects, Arrays, Dates, RegExps, TypedArrays, and ArrayBuffers. Any other
  * type of object requires application support. This callback must first use
  * the JS_WriteUint32Pair API to write an object header, passing a value
  * greater than JS_SCTAG_USER to the tag parameter. Then it can use the
  * JS_Write* APIs to write any other relevant parts of the value v to the
- * writer w.
+ * writer w. closure is any value passed to the JS_WriteStructuredCLone function.
  *
  * Return true on success, false on error/exception.
  */
-typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, JSObject *obj);
+typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w,
+                                         JSObject *obj, void *closure);
 
 /*
  * This is called when JS_WriteStructuredClone finds that the object to be
  * written is recursive. To follow HTML5, the application must throw a
  * DATA_CLONE_ERR DOMException. errorid is always JS_SCERR_RECURSION.
  */
 typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32 errorid);
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4202,17 +4202,17 @@ Wrap(JSContext *cx, uintN argc, jsval *v
 }
 
 JSBool
 Serialize(JSContext *cx, uintN argc, jsval *vp)
 {
     jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
     uint64 *datap;
     size_t nbytes;
-    if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes))
+    if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL))
         return false;
 
     JSObject *arrayobj = js_CreateTypedArray(cx, TypedArray::TYPE_UINT8, nbytes);
     if (!arrayobj) {
         JS_free(cx, datap);
         return false;
     }
     TypedArray *array = TypedArray::fromJSObject(arrayobj);
@@ -4234,17 +4234,17 @@ Deserialize(JSContext *cx, uintN argc, j
     }
     TypedArray *array = TypedArray::fromJSObject(obj);
     if ((uintptr_t(array->data) & 7) != 0) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_BAD_ALIGNMENT);
         return false;
     }
 
     if (!JS_ReadStructuredClone(cx, (uint64 *) array->data, array->byteLength,
-                                JS_STRUCTURED_CLONE_VERSION, &v)) {
+                                JS_STRUCTURED_CLONE_VERSION, &v, NULL, NULL)) {
         return false;
     }
     JS_SET_RVAL(cx, vp, v);
     return true;
 }
 
 JSBool
 SetGlobalPropIf(JSContext *cx, uintN argc, jsval *vp)
--- a/js/src/shell/jsworkers.cpp
+++ b/js/src/shell/jsworkers.cpp
@@ -289,30 +289,31 @@ class Event
     }
 
     void setChildAndRecipient(Worker *aChild, WorkerParent *aRecipient) {
         child = aChild;
         recipient = aRecipient;
     }
 
     bool deserializeData(JSContext *cx, jsval *vp) {
-        return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp);
+        return !!JS_ReadStructuredClone(cx, data, nbytes, JS_STRUCTURED_CLONE_VERSION, vp,
+                                        NULL, NULL);
     }
 
     virtual Result process(JSContext *cx) = 0;
 
     inline void trace(JSTracer *trc);
 
     template <class EventType>
     static EventType *createEvent(JSContext *cx, WorkerParent *recipient, Worker *child,
                                   jsval v)
     {
         uint64 *data;
         size_t nbytes;
-        if (!JS_WriteStructuredClone(cx, v, &data, &nbytes))
+        if (!JS_WriteStructuredClone(cx, v, &data, &nbytes, NULL, NULL))
             return NULL;
 
         EventType *event = new EventType;
         if (!event) {
             JS_ReportOutOfMemory(cx);
             return NULL;
         }
         event->recipient = recipient;