Bug 1246091 - patch 3/7 - Console API should store ConsoleCallData internally, r=ejpbruel
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 23 Mar 2016 22:55:07 +0100
changeset 290195 01b1b2a0b17cc4915aea179123d8eafbd718353d
parent 290194 25dff31b92ef2cc9dcde074895a70c7e36864fbe
child 290196 30134b3e46fa5564d0baa165b75783e394bf6ac2
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1246091
milestone48.0a1
Bug 1246091 - patch 3/7 - Console API should store ConsoleCallData internally, r=ejpbruel
dom/base/Console.cpp
dom/base/Console.h
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -50,16 +50,19 @@
 // The maximum stacktrace depth when populating the stacktrace array used for
 // console.trace().
 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
 
 // This tags are used in the Structured Clone Algorithm to move js values from
 // worker thread to main thread
 #define CONSOLE_TAG_BLOB   JS_SCTAG_USER_MIN
 
+// This value is taken from ConsoleAPIStorage.js
+#define STORAGE_MAX_EVENTS 200
+
 using namespace mozilla::dom::exceptions;
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 struct
 ConsoleStructuredCloneData
@@ -101,25 +104,26 @@ public:
   Initialize(JSContext* aCx, Console::MethodName aName,
              const nsAString& aString,
              const Sequence<JS::Value>& aArguments,
              Console* aConsole)
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(aConsole);
 
-    aConsole->RegisterConsoleCallData(this);
-    mConsole = aConsole;
+    // We must be registered before doing any JS operation otherwise it can
+    // happen that mCopiedArguments are not correctly traced.
+    aConsole->StoreCallData(this);
 
     mMethodName = aName;
     mMethodString = aString;
 
     for (uint32_t i = 0; i < aArguments.Length(); ++i) {
       if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
-        aConsole->UnregisterConsoleCallData(this);
+        aConsole->UnstoreCallData(this);
         return false;
       }
     }
 
     return true;
   }
 
   void
@@ -138,28 +142,16 @@ public:
     MOZ_ASSERT(mIDType == eUnknown);
 
     mOuterIDString = aOuterID;
     mInnerIDString = aInnerID;
     mIDType = eString;
   }
 
   void
-  CleanupJSObjects()
-  {
-    AssertIsOnOwningThread();
-    mCopiedArguments.Clear();
-
-    if (mConsole) {
-      mConsole->UnregisterConsoleCallData(this);
-      mConsole = nullptr;
-    }
-  }
-
-  void
   Trace(const TraceCallbacks& aCallbacks, void* aClosure)
   {
     AssertIsOnOwningThread();
 
     ConsoleCallData* tmp = this;
     for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
       NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
     }
@@ -182,35 +174,35 @@ public:
   int64_t mTimeStamp;
 
   // These values are set in the owning thread and they contain the timestamp of
   // when the new timer has started, the name of it and the status of the
   // creation of it. If status is false, something went wrong. User
   // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
   // monotonicTimer from Performance.now();
   // They will be set on the owning thread and never touched again on that
-  // thread. They will be used on the main-thread in order to create a
-  // ConsoleTimerStart dictionary when console.time() is used.
+  // thread. They will be used in order to create a ConsoleTimerStart dictionary
+  // when console.time() is used.
   DOMHighResTimeStamp mStartTimerValue;
   nsString mStartTimerLabel;
   bool mStartTimerStatus;
 
   // These values are set in the owning thread and they contain the duration,
   // the name and the status of the StopTimer method. If status is false,
   // something went wrong. They will be set on the owning thread and never
-  // touched again on that thread. They will be used on the main-thread in order
-  // to create a ConsoleTimerEnd dictionary. This members are set when
+  // touched again on that thread. They will be used in order to create a
+  // ConsoleTimerEnd dictionary. This members are set when
   // console.timeEnd() is called.
   double mStopTimerDuration;
   nsString mStopTimerLabel;
   bool mStopTimerStatus;
 
   // These 2 values are set by IncreaseCounter on the owning thread and they are
-  // used on the main-thread by CreateCounterValue. These members are set when
-  // console.count() is called.
+  // used CreateCounterValue. These members are set when console.count() is
+  // called.
   nsString mCountLabel;
   uint32_t mCountValue;
 
   // The concept of outerID and innerID is misleading because when a
   // ConsoleCallData is created from a window, these are the window IDs, but
   // when the object is created from a SharedWorker, a ServiceWorker or a
   // subworker of a ChromeWorker these IDs are the type of worker and the
   // filename of the callee.
@@ -234,28 +226,46 @@ public:
   // 1)  mTopStackFrame is initialized whenever we have any JS on the stack
   // 2)  mReifiedStack is initialized if we're created in a worker.
   // 3)  mStack is set (possibly to null if there is no JS on the stack) if
   //     we're created on main thread.
   Maybe<ConsoleStackEntry> mTopStackFrame;
   Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
   nsCOMPtr<nsIStackFrame> mStack;
 
+  // mStatus is about the lifetime of this object. Console must take care of
+  // keep it alive or not following this enumeration.
+  enum {
+    // If the object is created but it is owned by some runnable, this is its
+    // status. It can be deleted at any time.
+    eUnused,
+
+    // When a runnable takes ownership of a ConsoleCallData and send it to
+    // different thread, this is its status. Console cannot delete it at this
+    // time.
+    eInUse,
+
+    // When a runnable owns this ConsoleCallData, we can't delete it directly.
+    // instead, we mark it with this new status and we move it in
+    // mCallDataStoragePending list in order to keep it alive an trace it
+    // correctly. Once the runnable finishs its task, it will delete this object
+    // calling ReleaseCallData().
+    eToBeDeleted
+  } mStatus;
+
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
 private:
   ~ConsoleCallData()
   {
     AssertIsOnOwningThread();
-    CleanupJSObjects();
+    MOZ_ASSERT(mStatus != eInUse);
   }
-
-  RefPtr<Console> mConsole;
 };
 
 // This class is used to clear any exception at the end of this method.
 class ClearException
 {
 public:
   explicit ClearException(JSContext* aCx)
     : mCx(aCx)
@@ -288,29 +298,21 @@ public:
   {
     // Clear the StructuredCloneHolderBase class.
     Clear();
   }
 
   bool
   Dispatch(JS::Handle<JSObject*> aGlobal)
   {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-
-    JSContext* cx = mWorkerPrivate->GetJSContext();
-
-    if (NS_WARN_IF(!PreDispatch(cx, aGlobal))) {
+    if (!DispatchInternal(aGlobal)) {
+      ReleaseData();
       return false;
     }
 
-    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
-      return false;
-    }
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
     return true;
   }
 
   virtual bool Notify(JSContext* aCx, workers::Status aStatus) override
   {
     // We don't care about the notification. We just want to keep the
     // mWorkerPrivate alive.
     return true;
@@ -334,16 +336,38 @@ private:
     } else {
       RunWithWindow(window);
     }
 
     PostDispatch();
     return NS_OK;
   }
 
+  bool
+  DispatchInternal(JS::Handle<JSObject*> aGlobal)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+
+    if (NS_WARN_IF(!PreDispatch(cx, aGlobal))) {
+      return false;
+    }
+
+    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
+      return false;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) {
+      return false;
+    }
+
+    return true;
+  }
+
   void
   PostDispatch()
   {
     class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
     {
       RefPtr<ConsoleRunnable> mRunnable;
 
     public:
@@ -370,17 +394,17 @@ private:
 
     private:
       ~ConsoleReleaseRunnable()
       {}
     };
 
     RefPtr<WorkerControlRunnable> runnable =
       new ConsoleReleaseRunnable(mWorkerPrivate, this);
-    runnable->Dispatch();
+    NS_WARN_IF(!runnable->Dispatch());
   }
 
   void
   RunWithWindow(nsPIDOMWindowInner* aWindow)
   {
     AssertIsOnMainThread();
 
     AutoJSAPI jsapi;
@@ -509,19 +533,28 @@ protected:
 // the main-thread.
 class ConsoleCallDataRunnable final : public ConsoleRunnable
 {
 public:
   ConsoleCallDataRunnable(Console* aConsole,
                           ConsoleCallData* aCallData)
     : ConsoleRunnable(aConsole)
     , mCallData(aCallData)
-  { }
+  {
+    MOZ_ASSERT(aCallData);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mCallData->AssertIsOnOwningThread();
+  }
 
 private:
+  ~ConsoleCallDataRunnable()
+  {
+    MOZ_ASSERT(!mCallData);
+  }
+
   bool
   PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
     mCallData->AssertIsOnOwningThread();
 
     ClearException ce(aCx);
     JSAutoCompartment ac(aCx, aGlobal);
@@ -542,26 +575,25 @@ private:
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
     if (NS_WARN_IF(!Write(aCx, value))) {
       return false;
     }
 
-    mCallData->CleanupJSObjects();
+    mCallData->mStatus = ConsoleCallData::eInUse;
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
              nsPIDOMWindowInner* aInnerWindow) override
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mCallData->mCopiedArguments.IsEmpty());
 
     // The windows have to run in parallel.
     MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
 
     if (aOuterWindow) {
       mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
     } else {
       ConsoleStackEntry frame;
@@ -591,16 +623,25 @@ private:
     ProcessCallData(aCx);
 
     mClonedData.mParent = nullptr;
   }
 
   virtual void
   ReleaseData() override
   {
+    mConsole->AssertIsOnOwningThread();
+
+    if (mCallData->mStatus == ConsoleCallData::eToBeDeleted) {
+      mConsole->ReleaseCallData(mCallData);
+    } else {
+      MOZ_ASSERT(mCallData->mStatus == ConsoleCallData::eInUse);
+      mCallData->mStatus = ConsoleCallData::eUnused;
+    }
+
     mCallData = nullptr;
   }
 
   void
   ProcessCallData(JSContext* aCx)
   {
     AssertIsOnMainThread();
 
@@ -764,18 +805,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Co
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
-  for (uint32_t i = 0; i < tmp->mConsoleCallDataArray.Length(); ++i) {
-    tmp->mConsoleCallDataArray[i]->Trace(aCallbacks, aClosure);
+  for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
+    tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
+  }
+
+  for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
+    tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
 
@@ -869,17 +914,19 @@ Console::Shutdown()
     }
   }
 
   NS_ReleaseOnMainThread(mStorage.forget());
   NS_ReleaseOnMainThread(mSandbox.forget());
 
   mTimerRegistry.Clear();
   mCounterRegistry.Clear();
-  mConsoleCallDataArray.Clear();
+
+  mCallDataStorage.Clear();
+  mCallDataStoragePending.Clear();
 
   mStatus = eShuttingDown;
 }
 
 NS_IMETHODIMP
 Console::Observe(nsISupports* aSubject, const char* aTopic,
                  const char16_t* aData)
 {
@@ -1292,39 +1339,42 @@ Console::Method(JSContext* aCx, MethodNa
                                             callData->mCountLabel);
   }
 
   JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 
   if (NS_IsMainThread()) {
     callData->SetIDs(mOuterID, mInnerID);
     ProcessCallData(callData, global, aData);
+
+    // Just because we don't want to expose
+    // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
+    // cleanup the mCallDataStorage:
+    UnstoreCallData(callData);
     return;
   }
 
   RefPtr<ConsoleCallDataRunnable> runnable =
     new ConsoleCallDataRunnable(this, callData);
-  runnable->Dispatch(global);
+  NS_WARN_IF(!runnable->Dispatch(global));
 }
 
 // We store information to lazily compute the stack in the reserved slots of
 // LazyStackGetter.  The first slot always stores a JS object: it's either the
 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
 // reified the stack yet, or an UndefinedValue() otherwise.
 enum {
   SLOT_STACKOBJ,
   SLOT_RAW_STACK
 };
 
 bool
 LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
-  AssertIsOnMainThread();
-
   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
   JS::Rooted<JSObject*> callee(aCx, &args.callee());
 
   JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
   if (v.isUndefined()) {
     // Already reified.
     args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
     return true;
@@ -1354,179 +1404,27 @@ LazyStackGetter(JSContext* aCx, unsigned
 
 void
 Console::ProcessCallData(ConsoleCallData* aData, JS::Handle<JSObject*> aGlobal,
                          const Sequence<JS::Value>& aArguments)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aData);
 
-  ConsoleStackEntry frame;
-  if (aData->mTopStackFrame) {
-    frame = *aData->mTopStackFrame;
-  }
-
   AutoJSAPI jsapi;
   if (!jsapi.Init(aGlobal)) {
     return;
   }
   JSContext* cx = jsapi.cx();
-  ClearException ce(cx);
-  RootedDictionary<ConsoleEvent> event(cx);
-
-  event.mID.Construct();
-  event.mInnerID.Construct();
-
-  MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
-  if (aData->mIDType == ConsoleCallData::eString) {
-    event.mID.Value().SetAsString() = aData->mOuterIDString;
-    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
-  } else {
-    MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
-    event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
-    event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
-  }
-
-  event.mLevel = aData->mMethodString;
-  event.mFilename = frame.mFilename;
-
-  nsCOMPtr<nsIURI> filenameURI;
-  nsAutoCString pass;
-  if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
-      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
-    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
-    nsAutoCString spec;
-    if (safeURI &&
-        NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
-      CopyUTF8toUTF16(spec, event.mFilename);
-    }
-  }
-
-  event.mLineNumber = frame.mLineNumber;
-  event.mColumnNumber = frame.mColumnNumber;
-  event.mFunctionName = frame.mFunctionName;
-  event.mTimeStamp = aData->mTimeStamp;
-  event.mPrivate = aData->mPrivate;
-
-  switch (aData->mMethodName) {
-    case MethodLog:
-    case MethodInfo:
-    case MethodWarn:
-    case MethodError:
-    case MethodException:
-    case MethodDebug:
-    case MethodAssert:
-      event.mArguments.Construct();
-      event.mStyles.Construct();
-      if (!ProcessArguments(cx, aArguments, event.mArguments.Value(),
-                            event.mStyles.Value())) {
-        return;
-      }
-
-      break;
-
-    default:
-      event.mArguments.Construct();
-      if (!ArgumentsToValueList(aArguments, event.mArguments.Value())) {
-        return;
-      }
-  }
-
-  if (aData->mMethodName == MethodGroup ||
-      aData->mMethodName == MethodGroupCollapsed ||
-      aData->mMethodName == MethodGroupEnd) {
-    ComposeGroupName(cx, aArguments, event.mGroupName);
-  }
-
-  else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
-    event.mTimer = CreateStartTimerValue(cx, aData->mStartTimerLabel,
-                                         aData->mStartTimerValue,
-                                         aData->mStartTimerStatus);
-  }
-
-  else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
-    event.mTimer = CreateStopTimerValue(cx, aData->mStopTimerLabel,
-                                        aData->mStopTimerDuration,
-                                        aData->mStopTimerStatus);
-  }
-
-  else if (aData->mMethodName == MethodCount) {
-    event.mCounter = CreateCounterValue(cx, aData->mCountLabel,
-                                        aData->mCountValue);
-  }
-
-  // We want to create a console event object and pass it to our
-  // nsIConsoleAPIStorage implementation.  We want to define some accessor
-  // properties on this object, and those will need to keep an nsIStackFrame
-  // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
-  // further, passing untrusted objects to system code is likely to run afoul of
-  // Object Xrays.  So we want to wrap in a system-principal scope here.  But
-  // which one?  We could cheat and try to get the underlying JSObject* of
-  // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
-  // with explicit permission from the XPConnect module owner.  If you're
-  // tempted to do that anywhere else, talk to said module owner first.
-  JSAutoCompartment ac2(cx, xpc::PrivilegedJunkScope());
 
   JS::Rooted<JS::Value> eventValue(cx);
-  if (!ToJSValue(cx, event, &eventValue)) {
-    return;
-  }
-
-  JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
-  MOZ_ASSERT(eventObj);
-
-  if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
+  if (NS_WARN_IF(!PopulateEvent(cx, aGlobal, aArguments, &eventValue, aData))) {
     return;
   }
 
-  if (ShouldIncludeStackTrace(aData->mMethodName)) {
-    // Now define the "stacktrace" property on eventObj.  There are two cases
-    // here.  Either we came from a worker and have a reified stack, or we want
-    // to define a getter that will lazily reify the stack.
-    if (aData->mReifiedStack) {
-      JS::Rooted<JS::Value> stacktrace(cx);
-      if (!ToJSValue(cx, *aData->mReifiedStack, &stacktrace) ||
-          !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
-                             JSPROP_ENUMERATE)) {
-        return;
-      }
-    } else {
-      JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
-                                                    "stacktrace");
-      if (!fun) {
-        return;
-      }
-
-      JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
-
-      // We want to store our stack in the function and have it stay alive.  But
-      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
-      // wrapper and the raw pointer: the former will keep the latter alive.
-      JS::Rooted<JS::Value> stackVal(cx);
-      nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
-                                               &stackVal);
-      if (NS_FAILED(rv)) {
-        return;
-      }
-
-      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
-      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
-                                    JS::PrivateValue(aData->mStack.get()));
-
-      if (!JS_DefineProperty(cx, eventObj, "stacktrace",
-                             JS::UndefinedHandleValue,
-                             JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
-                             JSPROP_SETTER,
-                             JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
-                             nullptr)) {
-        return;
-      }
-    }
-  }
-
   if (!mStorage) {
     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
   }
 
   if (!mStorage) {
     NS_WARNING("Failed to get the ConsoleAPIStorage service.");
     return;
   }
@@ -1543,24 +1441,199 @@ Console::ProcessCallData(ConsoleCallData
     innerID.AppendInt(aData->mInnerIDNumber);
   }
 
   if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
     NS_WARNING("Failed to record a console event.");
   }
 }
 
+bool
+Console::PopulateEvent(JSContext* aCx,
+                       JS::Handle<JSObject*> aGlobal,
+                       const Sequence<JS::Value>& aArguments,
+                       JS::MutableHandle<JS::Value> aEventValue,
+                       ConsoleCallData* aData) const
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aData);
+
+  ConsoleStackEntry frame;
+  if (aData->mTopStackFrame) {
+    frame = *aData->mTopStackFrame;
+  }
+
+  ClearException ce(aCx);
+  RootedDictionary<ConsoleEvent> event(aCx);
+
+  JSAutoCompartment ac(aCx, aGlobal);
+
+  event.mID.Construct();
+  event.mInnerID.Construct();
+
+  if (aData->mIDType == ConsoleCallData::eString) {
+    event.mID.Value().SetAsString() = aData->mOuterIDString;
+    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
+  } else if (aData->mIDType == ConsoleCallData::eNumber) {
+    event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
+    event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
+  } else {
+    // aData->mIDType can be eUnknown when we dispatch notifications via
+    // mConsoleEventHandler.
+    event.mID.Value().SetAsUnsignedLongLong() = 0;
+    event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
+  }
+
+  event.mLevel = aData->mMethodString;
+  event.mFilename = frame.mFilename;
+
+  nsCOMPtr<nsIURI> filenameURI;
+  nsAutoCString pass;
+  if (NS_IsMainThread() &&
+      NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
+      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
+    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
+    nsAutoCString spec;
+    if (safeURI &&
+        NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
+      CopyUTF8toUTF16(spec, event.mFilename);
+    }
+  }
+
+  event.mLineNumber = frame.mLineNumber;
+  event.mColumnNumber = frame.mColumnNumber;
+  event.mFunctionName = frame.mFunctionName;
+  event.mTimeStamp = aData->mTimeStamp;
+  event.mPrivate = aData->mPrivate;
+
+  switch (aData->mMethodName) {
+    case MethodLog:
+    case MethodInfo:
+    case MethodWarn:
+    case MethodError:
+    case MethodException:
+    case MethodDebug:
+    case MethodAssert:
+      event.mArguments.Construct();
+      event.mStyles.Construct();
+      if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
+                                       event.mArguments.Value(),
+                                       event.mStyles.Value()))) {
+        return false;
+      }
+
+      break;
+
+    default:
+      event.mArguments.Construct();
+      if (NS_WARN_IF(!ArgumentsToValueList(aArguments,
+                                           event.mArguments.Value()))) {
+        return false;
+      }
+  }
+
+  if (aData->mMethodName == MethodGroup ||
+      aData->mMethodName == MethodGroupCollapsed ||
+      aData->mMethodName == MethodGroupEnd) {
+    ComposeGroupName(aCx, aArguments, event.mGroupName);
+  }
+
+  else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
+    event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
+                                         aData->mStartTimerValue,
+                                         aData->mStartTimerStatus);
+  }
+
+  else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
+    event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
+                                        aData->mStopTimerDuration,
+                                        aData->mStopTimerStatus);
+  }
+
+  else if (aData->mMethodName == MethodCount) {
+    event.mCounter = CreateCounterValue(aCx, aData->mCountLabel,
+                                        aData->mCountValue);
+  }
+
+  // We want to create a console event object and pass it to our
+  // nsIConsoleAPIStorage implementation.  We want to define some accessor
+  // properties on this object, and those will need to keep an nsIStackFrame
+  // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
+  // further, passing untrusted objects to system code is likely to run afoul of
+  // Object Xrays.  So we want to wrap in a system-principal scope here.  But
+  // which one?  We could cheat and try to get the underlying JSObject* of
+  // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
+  // with explicit permission from the XPConnect module owner.  If you're
+  // tempted to do that anywhere else, talk to said module owner first.
+  JSAutoCompartment ac2(aCx, xpc::PrivilegedJunkScope());
+
+  if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
+    return false;
+  }
+
+  JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
+  if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
+                                    JSPROP_ENUMERATE))) {
+    return false;
+  }
+
+  if (ShouldIncludeStackTrace(aData->mMethodName)) {
+    // Now define the "stacktrace" property on eventObj.  There are two cases
+    // here.  Either we came from a worker and have a reified stack, or we want
+    // to define a getter that will lazily reify the stack.
+    if (aData->mReifiedStack) {
+      JS::Rooted<JS::Value> stacktrace(aCx);
+      if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
+          NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
+                                        JSPROP_ENUMERATE))) {
+        return false;
+      }
+    } else {
+      JSFunction* fun = js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0,
+                                                    "stacktrace");
+      if (NS_WARN_IF(!fun)) {
+        return false;
+      }
+
+      JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
+
+      // We want to store our stack in the function and have it stay alive.  But
+      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
+      // wrapper and the raw pointer: the former will keep the latter alive.
+      JS::Rooted<JS::Value> stackVal(aCx);
+      nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack,
+                                               &stackVal);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
+      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
+                                    JS::PrivateValue(aData->mStack.get()));
+
+      if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace",
+                                        JS::UndefinedHandleValue,
+                                        JSPROP_ENUMERATE | JSPROP_SHARED |
+                                        JSPROP_GETTER | JSPROP_SETTER,
+                                        JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
+                                        nullptr))) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 namespace {
 
 // Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
 bool
 FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
 {
-  AssertIsOnMainThread();
-
   if (!aOutput.IsEmpty()) {
     JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
                                                        aOutput.get(),
                                                        aOutput.Length()));
     if (NS_WARN_IF(!str)) {
       return false;
     }
 
@@ -1577,18 +1650,16 @@ FlushOutput(JSContext* aCx, Sequence<JS:
 } // namespace
 
 bool
 Console::ProcessArguments(JSContext* aCx,
                           const Sequence<JS::Value>& aData,
                           Sequence<JS::Value>& aSequence,
                           Sequence<nsString>& aStyles) const
 {
-  AssertIsOnMainThread();
-
   if (aData.IsEmpty()) {
     return true;
   }
 
   if (aData.Length() == 1 || !aData[0].isString()) {
     return ArgumentsToValueList(aData, aSequence);
   }
 
@@ -1821,18 +1892,16 @@ Console::ProcessArguments(JSContext* aCx
 
   return true;
 }
 
 void
 Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
                           int32_t aMantissa, char aCh) const
 {
-  AssertIsOnMainThread();
-
   aFormat.Append('%');
   if (aInteger >= 0) {
     aFormat.AppendInt(aInteger);
   }
 
   if (aMantissa >= 0) {
     aFormat.Append('.');
     aFormat.AppendInt(aMantissa);
@@ -1841,18 +1910,16 @@ Console::MakeFormatString(nsCString& aFo
   aFormat.Append(aCh);
 }
 
 void
 Console::ComposeGroupName(JSContext* aCx,
                           const Sequence<JS::Value>& aData,
                           nsAString& aName) const
 {
-  AssertIsOnMainThread();
-
   for (uint32_t i = 0; i < aData.Length(); ++i) {
     if (i != 0) {
       aName.AppendASCII(" ");
     }
 
     JS::Rooted<JS::Value> value(aCx, aData[i]);
     JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
     if (!jsString) {
@@ -1906,18 +1973,16 @@ Console::StartTimer(JSContext* aCx, cons
   return true;
 }
 
 JS::Value
 Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                                DOMHighResTimeStamp aTimerValue,
                                bool aTimerStatus) const
 {
-  AssertIsOnMainThread();
-
   if (!aTimerStatus) {
     RootedDictionary<ConsoleTimerError> error(aCx);
 
     JS::Rooted<JS::Value> value(aCx);
     if (!ToJSValue(aCx, error, &value)) {
       return JS::UndefinedValue();
     }
 
@@ -1970,18 +2035,16 @@ Console::StopTimer(JSContext* aCx, const
   *aTimerDuration = aTimestamp - entry;
   return true;
 }
 
 JS::Value
 Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
                               double aDuration, bool aStatus) const
 {
-  AssertIsOnMainThread();
-
   if (!aStatus) {
     return JS::UndefinedValue();
   }
 
   RootedDictionary<ConsoleTimerEnd> timer(aCx);
   timer.mName = aLabel;
   timer.mDuration = aDuration;
 
@@ -1992,18 +2055,16 @@ Console::CreateStopTimerValue(JSContext*
 
   return value;
 }
 
 bool
 Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
                               Sequence<JS::Value>& aSequence) const
 {
-  AssertIsOnMainThread();
-
   for (uint32_t i = 0; i < aData.Length(); ++i) {
     if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
       return false;
     }
   }
 
   return true;
 }
@@ -2049,18 +2110,16 @@ Console::IncreaseCounter(JSContext* aCx,
   aCountLabel = label;
   return count;
 }
 
 JS::Value
 Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
                             uint32_t aCountValue) const
 {
-  AssertIsOnMainThread();
-
   ClearException ce(aCx);
 
   if (aCountValue == MAX_PAGE_COUNTERS) {
     RootedDictionary<ConsoleCounterError> error(aCx);
 
     JS::Rooted<JS::Value> value(aCx);
     if (!ToJSValue(aCx, error, &value)) {
       return JS::UndefinedValue();
@@ -2112,31 +2171,62 @@ Console::GetOrCreateSandbox(JSContext* a
 
     mSandbox = new JSObjectHolder(aCx, sandbox);
   }
 
   return mSandbox->GetJSObject();
 }
 
 void
-Console::RegisterConsoleCallData(ConsoleCallData* aData)
+Console::StoreCallData(ConsoleCallData* aCallData)
 {
   AssertIsOnOwningThread();
 
-  MOZ_ASSERT(!mConsoleCallDataArray.Contains(aData));
-  mConsoleCallDataArray.AppendElement(aData);
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
+  MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStorage.AppendElement(aCallData);
+
+  if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
+    RefPtr<ConsoleCallData> callData = mCallDataStorage[0];
+    mCallDataStorage.RemoveElementAt(0);
+
+    MOZ_ASSERT(callData->mStatus != ConsoleCallData::eToBeDeleted);
+
+    // We cannot delete this object now because we have to trace its JSValues
+    // until the pending operation (ConsoleCallDataRunnable) is completed.
+    if (callData->mStatus == ConsoleCallData::eInUse) {
+      callData->mStatus = ConsoleCallData::eToBeDeleted;
+      mCallDataStoragePending.AppendElement(callData);
+    }
+  }
 }
 
 void
-Console::UnregisterConsoleCallData(ConsoleCallData* aData)
+Console::UnstoreCallData(ConsoleCallData* aCallData)
 {
   AssertIsOnOwningThread();
 
-  MOZ_ASSERT(mConsoleCallDataArray.Contains(aData));
-  mConsoleCallDataArray.RemoveElement(aData);
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(mCallDataStorage.Contains(aCallData));
+  MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStorage.RemoveElement(aCallData);
+}
+
+void
+Console::ReleaseCallData(ConsoleCallData* aCallData)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eToBeDeleted);
+  MOZ_ASSERT(mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStoragePending.RemoveElement(aCallData);
 }
 
 void
 Console::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mOwningThread);
   MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
 }
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -152,16 +152,33 @@ private:
   Method(JSContext* aCx, MethodName aName, const nsAString& aString,
          const Sequence<JS::Value>& aData);
 
   void
   ProcessCallData(ConsoleCallData* aData,
                   JS::Handle<JSObject*> aGlobal,
                   const Sequence<JS::Value>& aArguments);
 
+  void
+  StoreCallData(ConsoleCallData* aData);
+
+  void
+  UnstoreCallData(ConsoleCallData* aData);
+
+  // Read in Console.cpp how this method is used.
+  void
+  ReleaseCallData(ConsoleCallData* aCallData);
+
+  bool
+  PopulateEvent(JSContext* aCx,
+                JS::Handle<JSObject*> aGlobal,
+                const Sequence<JS::Value>& aArguments,
+                JS::MutableHandle<JS::Value> aValue,
+                ConsoleCallData* aData) const;
+
   // If the first JS::Value of the array is a string, this method uses it to
   // format a string. The supported sequences are:
   //   %s    - string
   //   %d,%i - integer
   //   %f    - double
   //   %o,%O - a JS object.
   //   %c    - style string.
   // The output is an array where any object is a separated item, the rest is
@@ -203,51 +220,50 @@ private:
   // * aTimerValue - the StartTimer value stored into (or taken from)
   //                 mTimerRegistry.
   bool
   StartTimer(JSContext* aCx, const JS::Value& aName,
              DOMHighResTimeStamp aTimestamp,
              nsAString& aTimerLabel,
              DOMHighResTimeStamp* aTimerValue);
 
-  // CreateStartTimerValue is called on the main thread only and generates a
-  // ConsoleTimerStart dictionary exposed as JS::Value. If aTimerStatus is
-  // false, it generates a ConsoleTimerError instead. It's called only after
-  // the execution StartTimer on the owning thread.
+  // CreateStartTimerValue generates a ConsoleTimerStart dictionary exposed as
+  // JS::Value. If aTimerStatus is false, it generates a ConsoleTimerError
+  // instead. It's called only after the execution StartTimer on the owning
+  // thread.
   // * aCx - this is the context that will root the returned value.
   // * aTimerLabel - this label must be what StartTimer received as aTimerLabel.
   // * aTimerValue - this is what StartTimer received as aTimerValue
   // * aTimerStatus - the return value of StartTimer.
   JS::Value
   CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                         DOMHighResTimeStamp aTimerValue,
                         bool aTimerStatus) const;
 
   // StopTimer follows the same pattern as StartTimer: it runs on the
   // owning thread and populates aTimerLabel and aTimerDuration, used by
-  // CreateStopTimerValue on the main thread. It returns false if a JS
-  // exception is thrown or if the aName timer doesn't exist in mTimerRegistry.
+  // CreateStopTimerValue. It returns false if a JS exception is thrown or if
+  // the aName timer doesn't exist in the mTimerRegistry.
   // * aCx - the JSContext rooting aName.
   // * aName - this is (should be) the name of the timer as JS::Value.
   // * aTimestamp - the monotonicTimer for this context (taken from
   //                window->performance.now() or from Now() -
   //                workerPrivate->NowBaseTimeStamp() in workers.
   // * aTimerLabel - This label will be populated with the aName converted to a
   //                 string.
   // * aTimerDuration - the difference between aTimestamp and when the timer
   //                    started (see StartTimer).
   bool
   StopTimer(JSContext* aCx, const JS::Value& aName,
             DOMHighResTimeStamp aTimestamp,
             nsAString& aTimerLabel,
             double* aTimerDuration);
 
-  // Executed on the main thread and generates a ConsoleTimerEnd dictionary
-  // exposed as JS::Value, or a ConsoleTimerError dictionary if aTimerStatus is
-  // false. See StopTimer.
+  // This method generates a ConsoleTimerEnd dictionary exposed as JS::Value, or
+  // a ConsoleTimerError dictionary if aTimerStatus is false. See StopTimer.
   // * aCx - this is the context that will root the returned value.
   // * aTimerLabel - this label must be what StopTimer received as aTimerLabel.
   // * aTimerDuration - this is what StopTimer received as aTimerDuration
   // * aTimerStatus - the return value of StopTimer.
   JS::Value
   CreateStopTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                        double aTimerDuration,
                        bool aTimerStatus) const;
@@ -257,66 +273,69 @@ private:
   ArgumentsToValueList(const Sequence<JS::Value>& aData,
                        Sequence<JS::Value>& aSequence) const;
 
   void
   ProfileMethod(JSContext* aCx, const nsAString& aAction,
                 const Sequence<JS::Value>& aData);
 
   // This method follows the same pattern as StartTimer: its runs on the owning
-  // thread and populates aCountLabel, used by CreateCounterValue on the
-  // main thread. Returns MAX_PAGE_COUNTERS in case of error otherwise the
-  // incremented counter value.
+  // thread and populate aCountLabel, used by CreateCounterValue. Returns
+  // MAX_PAGE_COUNTERS in case of error, otherwise the incremented counter
+  // value.
   // * aCx - the JSContext rooting aData.
   // * aFrame - the first frame of ConsoleCallData.
   // * aData - the arguments received by the console.count() method.
   // * aCountLabel - the label that will be populated by this method.
   uint32_t
   IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
                   const Sequence<JS::Value>& aData,
                   nsAString& aCountLabel);
 
-  // Executed on the main thread and generates a ConsoleCounter dictionary as
-  // JS::Value. If aCountValue is == MAX_PAGE_COUNTERS it generates a
-  // ConsoleCounterError instead. See IncreaseCounter.
+  // This method generates a ConsoleCounter dictionary as JS::Value. If
+  // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
+  // instead. See IncreaseCounter.
   // * aCx - this is the context that will root the returned value.
   // * aCountLabel - this label must be what IncreaseCounter received as
   //                 aTimerLabel.
   // * aCountValue - the return value of IncreaseCounter.
   JS::Value
   CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
                      uint32_t aCountValue) const;
 
   bool
   ShouldIncludeStackTrace(MethodName aMethodName) const;
 
   JSObject*
   GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
 
   void
-  RegisterConsoleCallData(ConsoleCallData* aData);
-
-  void
-  UnregisterConsoleCallData(ConsoleCallData* aData);
-
-  void
   AssertIsOnOwningThread() const;
 
   // All these nsCOMPtr are touched on main thread only.
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
   RefPtr<JSObjectHolder> mSandbox;
 
   // Touched on the owner thread.
   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
 
-  // Raw pointers because ConsoleCallData manages its own
-  // registration/unregistration.
-  nsTArray<ConsoleCallData*> mConsoleCallDataArray;
+  nsTArray<RefPtr<ConsoleCallData>> mCallDataStorage;
+
+  // This array is used in a particular corner-case where:
+  // 1. we are in a worker thread
+  // 2. we have more than STORAGE_MAX_EVENTS
+  // 3. but the main-thread ConsoleCallDataRunnable of the first one is still
+  // running (this means that something very bad is happening on the
+  // main-thread).
+  // When this happens we want to keep the ConsoleCallData alive for traceing
+  // its JSValues also if 'officially' this ConsoleCallData must be removed from
+  // the storage.
+  nsTArray<RefPtr<ConsoleCallData>> mCallDataStoragePending;
 
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
   uint64_t mOuterID;
   uint64_t mInnerID;