Bug 1246091 - Expose ConsoleEvents to Worker Debugger Actors via Console object directly, r=ejpbruel
☠☠ backed out by 3f3abdbf2d06 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 11 Mar 2016 17:59:52 +0100
changeset 288319 50c03319c341910196103c4451f17299eb144313
parent 288318 552aabbcd11631edd38087a55e8730db1384bdfc
child 288320 21884d000eded02b02e4c221ba9f80ac03fe53f6
push id30079
push userryanvm@gmail.com
push dateSat, 12 Mar 2016 20:24:19 +0000
treeherdermozilla-central@d1d47ba19ce9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs1246091
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1246091 - Expose ConsoleEvents to Worker Debugger Actors via Console object directly, r=ejpbruel
dom/base/Console.cpp
dom/base/Console.h
dom/base/nsGlobalWindow.cpp
dom/bindings/Bindings.conf
dom/webidl/WorkerDebuggerGlobalScope.webidl
dom/webidl/WorkerGlobalScope.webidl
dom/workers/RuntimeService.cpp
dom/workers/RuntimeService.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/WorkerScope.cpp
dom/workers/WorkerScope.h
dom/workers/test/WorkerDebugger.console_childWorker.js
dom/workers/test/WorkerDebugger.console_debugger.js
dom/workers/test/WorkerDebugger.console_worker.js
dom/workers/test/chrome.ini
dom/workers/test/test_WorkerDebugger_console.xul
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/ConsoleBinding.h"
 
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
@@ -50,16 +51,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
@@ -90,38 +94,42 @@ public:
     , mStopTimerStatus(false)
     , mCountValue(MAX_PAGE_COUNTERS)
     , mIDType(eUnknown)
     , mOuterIDNumber(0)
     , mInnerIDNumber(0)
 #ifdef DEBUG
     , mOwningThread(PR_GetCurrentThread())
 #endif
-  { }
-
-  void
+  {}
+
+  bool
   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 (!mCopiedArguments.AppendElement(aArguments[i])) {
-        return;
+      if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
+        aConsole->UnstoreCallData(this);
+        return false;
       }
     }
+
+    return true;
   }
 
   void
   SetIDs(uint64_t aOuterID, uint64_t aInnerID)
   {
     MOZ_ASSERT(mIDType == eUnknown);
 
     mOuterIDNumber = aOuterID;
@@ -134,26 +142,29 @@ public:
   {
     MOZ_ASSERT(mIDType == eUnknown);
 
     mOuterIDString = aOuterID;
     mInnerIDString = aInnerID;
     mIDType = eString;
   }
 
-  void
-  CleanupJSObjects()
+  bool
+  PopulateSequenceArguments(Sequence<JS::Value>& aSequence) const
   {
     AssertIsOnOwningThread();
-    mCopiedArguments.Clear();
-
-    if (mConsole) {
-      mConsole->UnregisterConsoleCallData(this);
-      mConsole = nullptr;
+
+    for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
+      if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
+                                              fallible))) {
+        return false;
+      }
     }
+
+    return true;
   }
 
   void
   Trace(const TraceCallbacks& aCallbacks, void* aClosure)
   {
     AssertIsOnOwningThread();
 
     ConsoleCallData* tmp = this;
@@ -179,35 +190,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.
@@ -231,28 +242,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)
@@ -289,17 +318,17 @@ public:
 
   bool
   Dispatch(JS::Handle<JSObject*> aGlobal)
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     JSContext* cx = mWorkerPrivate->GetJSContext();
 
-    if (!PreDispatch(cx, aGlobal)) {
+    if (NS_WARN_IF(!PreDispatch(cx, aGlobal))) {
       return false;
     }
 
     if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
       return false;
     }
 
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
@@ -309,17 +338,18 @@ public:
   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;
   }
 
 private:
-  NS_IMETHOD Run() override
+  NS_IMETHOD
+  Run() override
   {
     AssertIsOnMainThread();
 
     // Walk up to our containing page
     WorkerPrivate* wp = mWorkerPrivate;
     while (wp->GetParent()) {
       wp = wp->GetParent();
     }
@@ -466,32 +496,32 @@ protected:
 
   virtual bool CustomWriteHandler(JSContext* aCx,
                                   JSStructuredCloneWriter* aWriter,
                                   JS::Handle<JSObject*> aObj) override
   {
     RefPtr<Blob> blob;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
         blob->Impl()->MayBeClonedToOtherThreads()) {
-      if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
-                              mClonedData.mBlobs.Length())) {
+      if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
+                                         mClonedData.mBlobs.Length()))) {
         return false;
       }
 
       mClonedData.mBlobs.AppendElement(blob->Impl());
       return true;
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
     JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
-    if (!jsString) {
+    if (NS_WARN_IF(!jsString)) {
       return false;
     }
 
-    if (!JS_WriteString(aWriter, jsString)) {
+    if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
       return false;
     }
 
     return true;
   }
 
   WorkerPrivate* mWorkerPrivate;
 
@@ -505,57 +535,60 @@ protected:
 // the main-thread.
 class ConsoleCallDataRunnable final : public ConsoleRunnable
 {
 public:
   ConsoleCallDataRunnable(Console* aConsole,
                           ConsoleCallData* aCallData)
     : ConsoleRunnable(aConsole)
     , mCallData(aCallData)
-  { }
+  {
+    MOZ_ASSERT(aCallData);
+    mCallData->mStatus = ConsoleCallData::eInUse;
+  }
 
 private:
   bool
   PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
+    mCallData->AssertIsOnOwningThread();
 
     ClearException ce(aCx);
     JSAutoCompartment ac(aCx, aGlobal);
 
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
-    if (!arguments) {
+    if (NS_WARN_IF(!arguments)) {
       return false;
     }
 
     JS::Rooted<JS::Value> arg(aCx);
     for (uint32_t i = 0; i < mCallData->mCopiedArguments.Length(); ++i) {
       arg = mCallData->mCopiedArguments[i];
-      if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
+      if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
+                                       JSPROP_ENUMERATE))) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!Write(aCx, value)) {
+    if (NS_WARN_IF(!Write(aCx, value))) {
       return false;
     }
 
-    mCallData->CleanupJSObjects();
     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;
@@ -585,16 +618,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();
 
@@ -631,17 +673,20 @@ private:
 
     MOZ_ASSERT(values.Length() == length);
 
     JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 
     mConsole->ProcessCallData(mCallData, global, values);
   }
 
-  RefPtr<ConsoleCallData> mCallData;
+  // We don't have to keep this object alive because this runnable is holding a
+  // reference of the Console object and this CallData is kept alive by that
+  // Console object.
+  ConsoleCallData* MOZ_NON_OWNING_REF mCallData;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable final : public ConsoleRunnable
 {
 public:
   ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
                          const Sequence<JS::Value>& aArguments)
@@ -654,39 +699,40 @@ public:
 
 private:
   bool
   PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
   {
     ClearException ce(aCx);
 
     JS::Rooted<JSObject*> global(aCx, aGlobal);
-    if (!global) {
+    if (NS_WARN_IF(!global)) {
       return false;
     }
 
     JSAutoCompartment ac(aCx, global);
 
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mArguments.Length()));
-    if (!arguments) {
+    if (NS_WARN_IF(!arguments)) {
       return false;
     }
 
     JS::Rooted<JS::Value> arg(aCx);
     for (uint32_t i = 0; i < mArguments.Length(); ++i) {
       arg = mArguments[i];
-      if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
+      if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
+                                       JSPROP_ENUMERATE))) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!Write(aCx, value)) {
+    if (NS_WARN_IF(!Write(aCx, value))) {
       return false;
     }
 
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
@@ -747,121 +793,190 @@ private:
 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
 // We don't need to traverse/unlink mStorage and mSandbox because they are not
 // CCed objects and they are only used on the main thread, even when this
 // Console object is used on workers.
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventHandler)
+  tmp->Shutdown();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventHandler)
   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)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
+/* static */ already_AddRefed<Console>
+Console::Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv)
+{
+  RefPtr<Console> console = new Console(aWindow);
+  console->Initialize(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return console.forget();
+}
+
 Console::Console(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 #ifdef DEBUG
   , mOwningThread(PR_GetCurrentThread())
 #endif
   , mOuterID(0)
   , mInnerID(0)
+  , mStatus(eUnknown)
 {
   if (mWindow) {
     MOZ_ASSERT(mWindow->IsInnerWindow());
     mInnerID = mWindow->WindowID();
 
     // Without outerwindow any console message coming from this object will not
     // shown in the devtools webconsole. But this should be fine because
     // probably we are shutting down, or the window is CCed/GCed.
     nsPIDOMWindowOuter* outerWindow = mWindow->GetOuterWindow();
     if (outerWindow) {
       mOuterID = outerWindow->WindowID();
     }
   }
+}
+
+Console::~Console()
+{
+  AssertIsOnOwningThread();
+  Shutdown();
+}
+
+void
+Console::Initialize(ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eUnknown);
+
+  mStatus = eInitialized;
 
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      obs->AddObserver(this, "inner-window-destroyed", true);
+    if (NS_WARN_IF(!obs)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+
+    aRv = obs->AddObserver(this, "inner-window-destroyed", true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    aRv = obs->AddObserver(this, "memory-pressure", true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
     }
   }
 
   mozilla::HoldJSObjects(this);
 }
 
-Console::~Console()
+void
+Console::Shutdown()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mConsoleCallDataArray.IsEmpty());
-
-  if (!NS_IsMainThread()) {
+
+  if (mStatus == eUnknown || mStatus == eShuttingDown) {
+    return;
+  }
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs =
+      do_GetService("@mozilla.org/observer-service;1");
+    if (obs) {
+      obs->RemoveObserver(this, "inner-window-destroyed");
+      obs->RemoveObserver(this, "memory-pressure");
+    }
+  } else {
     if (mStorage) {
       NS_ReleaseOnMainThread(mStorage.forget());
     }
 
     if (mSandbox) {
       NS_ReleaseOnMainThread(mSandbox.forget());
     }
   }
 
+  mStatus = eShuttingDown;
+
+  mTimerRegistry.Clear();
+
+  mCallDataStorage.Clear();
+  mCallDataStoragePending.Clear();
+
   mozilla::DropJSObjects(this);
 }
 
 NS_IMETHODIMP
 Console::Observe(nsISupports* aSubject, const char* aTopic,
                  const char16_t* aData)
 {
   AssertIsOnMainThread();
 
-  if (strcmp(aTopic, "inner-window-destroyed")) {
+  if (!strcmp(aTopic, "inner-window-destroyed")) {
+    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+    uint64_t innerID;
+    nsresult rv = wrapper->GetData(&innerID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (innerID == mInnerID) {
+      Shutdown();
+    }
+
     return NS_OK;
   }
 
-  nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
-  NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
-
-  uint64_t innerID;
-  nsresult rv = wrapper->GetData(&innerID);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (innerID == mInnerID) {
-    nsCOMPtr<nsIObserverService> obs =
-      do_GetService("@mozilla.org/observer-service;1");
-    if (obs) {
-      obs->RemoveObserver(this, "inner-window-destroyed");
-    }
-
-    mTimerRegistry.Clear();
+  if (!strcmp(aTopic, "memory-pressure")) {
+    ClearStorage();
+    return NS_OK;
   }
 
   return NS_OK;
 }
 
+void
+Console::ClearStorage()
+{
+  mCallDataStorage.Clear();
+}
+
 JSObject*
 Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ConsoleBinding::Wrap(aCx, this, aGivenProto);
 }
 
 #define METHOD(name, string)                                          \
   void                                                                \
@@ -877,16 +992,17 @@ METHOD(Error, "error")
 METHOD(Exception, "exception")
 METHOD(Debug, "debug")
 METHOD(Table, "table")
 
 void
 Console::Trace(JSContext* aCx)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   const Sequence<JS::Value> data;
   Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
 }
 
 // Displays an interactive listing of all the properties of an object.
 METHOD(Dir, "dir");
 METHOD(Dirxml, "dirxml");
@@ -894,75 +1010,84 @@ METHOD(Dirxml, "dirxml");
 METHOD(Group, "group")
 METHOD(GroupCollapsed, "groupCollapsed")
 METHOD(GroupEnd, "groupEnd")
 
 void
 Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
     return;
   }
 
   Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
 }
 
 void
 Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
     return;
   }
 
   Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
 }
 
 void
 Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (aData.isString() && !data.AppendElement(aData, fallible)) {
     return;
   }
 
   Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
 }
 
 void
 Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
+
   ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
 }
 
 void
 Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
+
   ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
 }
 
 void
 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
                        const Sequence<JS::Value>& aData)
 {
+  MOZ_ASSERT(mStatus == eInitialized);
+
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     RefPtr<ConsoleProfileRunnable> runnable =
       new ConsoleProfileRunnable(this, aAction, aData);
 
     JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
     runnable->Dispatch(global);
     return;
@@ -1010,28 +1135,30 @@ Console::ProfileMethod(JSContext* aCx, c
   }
 }
 
 void
 Console::Assert(JSContext* aCx, bool aCondition,
                 const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   if (!aCondition) {
     Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
   }
 }
 
 METHOD(Count, "count")
 
 void
 Console::NoopMethod()
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   // Nothing to do.
 }
 
 static
 nsresult
 StackFrameToStackEntry(nsIStackFrame* aStackFrame,
                        ConsoleStackEntry& aStackEntry,
@@ -1101,22 +1228,26 @@ ReifyStack(nsIStackFrame* aStack, nsTArr
 
 // Queue a call to a console method. See the CALL_DELAY constant.
 void
 Console::Method(JSContext* aCx, MethodName aMethodName,
                 const nsAString& aMethodString,
                 const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
-  callData->Initialize(aCx, aMethodName, aMethodString, aData, this);
+  if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
+                                       aData, this))) {
+    return;
+  }
 
   if (mWindow) {
     nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
     if (!webNav) {
       return;
     }
 
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
@@ -1261,19 +1392,28 @@ 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;
   }
 
+  // We do this only in workers for now.
+  NotifyHandler(aCx, global, aData, callData);
+
+  // Moving the ownership of callData to the runnable.
   RefPtr<ConsoleCallDataRunnable> runnable =
     new ConsoleCallDataRunnable(this, callData);
   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.
@@ -1282,38 +1422,36 @@ Console::Method(JSContext* aCx, MethodNa
 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;
   }
 
   nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
   nsTArray<ConsoleStackEntry> reifiedStack;
   nsresult rv = ReifyStack(stack, reifiedStack);
-  if (NS_FAILED(rv)) {
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     Throw(aCx, rv);
     return false;
   }
 
   JS::Rooted<JS::Value> stackVal(aCx);
-  if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
+  if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
     return false;
   }
 
   MOZ_ASSERT(stackVal.isObject());
 
   js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
   js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
 
@@ -1323,177 +1461,35 @@ 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;
-  }
-
   AutoSafeJSContext cx;
-  ClearException ce(cx);
-  RootedDictionary<ConsoleEvent> event(cx);
-
-  JSAutoCompartment ac(cx, aGlobal);
-
-  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);
-  }
+  JS::Rooted<JS::Value> eventValue(cx);
 
   // 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;
   }
@@ -1510,33 +1506,196 @@ 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);
+  }
+
+  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 (!str) {
+    if (NS_WARN_IF(!str)) {
       return false;
     }
 
-    if (!aSequence.AppendElement(JS::StringValue(str), fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
       return false;
     }
 
     aOutput.Truncate();
   }
 
   return true;
 }
@@ -1544,34 +1703,32 @@ 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);
   }
 
   JS::Rooted<JS::Value> format(aCx, aData[0]);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString string;
-  if (!string.init(aCx, jsString)) {
+  if (NS_WARN_IF(!string.init(aCx, jsString))) {
     return false;
   }
 
   nsString::const_iterator start, end;
   string.BeginReading(start);
   string.EndReading(end);
 
   nsString output;
@@ -1650,111 +1807,112 @@ Console::ProcessArguments(JSContext* aCx
     char ch = *start;
     tmp.Append(ch);
     ++start;
 
     switch (ch) {
       case 'o':
       case 'O':
       {
-        if (!FlushOutput(aCx, aSequence, output)) {
+        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
           return false;
         }
 
         JS::Rooted<JS::Value> v(aCx);
         if (index < aData.Length()) {
           v = aData[index++];
         }
 
-        if (!aSequence.AppendElement(v, fallible)) {
+        if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
           return false;
         }
 
         break;
       }
 
       case 'c':
       {
         // If there isn't any output but there's already a style, then
         // discard the previous style and use the next one instead.
         if (output.IsEmpty() && !aStyles.IsEmpty()) {
           aStyles.TruncateLength(aStyles.Length() - 1);
         }
 
-        if (!FlushOutput(aCx, aSequence, output)) {
+        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
           return false;
         }
 
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> v(aCx, aData[index++]);
           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
-          if (!jsString) {
+          if (NS_WARN_IF(!jsString)) {
             return false;
           }
 
           int32_t diff = aSequence.Length() - aStyles.Length();
           if (diff > 0) {
             for (int32_t i = 0; i < diff; i++) {
-              if (!aStyles.AppendElement(NullString(), fallible)) {
+              if (NS_WARN_IF(!aStyles.AppendElement(NullString(),
+                                                    fallible))) {
                 return false;
               }
             }
           }
 
           nsAutoJSString string;
           if (!string.init(aCx, jsString)) {
             return false;
           }
 
-          if (!aStyles.AppendElement(string, fallible)) {
+          if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
             return false;
           }
         }
         break;
       }
 
       case 's':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
-          if (!jsString) {
+          if (NS_WARN_IF(!jsString)) {
             return false;
           }
 
           nsAutoJSString v;
-          if (!v.init(aCx, jsString)) {
+          if (NS_WARN_IF(!v.init(aCx, jsString))) {
             return false;
           }
 
           output.Append(v);
         }
         break;
 
       case 'd':
       case 'i':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
 
           int32_t v;
-          if (!JS::ToInt32(aCx, value, &v)) {
+          if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
             return false;
           }
 
           nsCString format;
           MakeFormatString(format, integer, mantissa, 'd');
           output.AppendPrintf(format.get(), v);
         }
         break;
 
       case 'f':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
 
           double v;
-          if (!JS::ToNumber(aCx, value, &v)) {
+          if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
             return false;
           }
 
           // nspr returns "nan", but we want to expose it as "NaN"
           if (std::isnan(v)) {
             output.AppendFloat(v);
           } else {
             nsCString format;
@@ -1765,41 +1923,39 @@ Console::ProcessArguments(JSContext* aCx
         break;
 
       default:
         output.Append(tmp);
         break;
     }
   }
 
-  if (!FlushOutput(aCx, aSequence, output)) {
+  if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
     return false;
   }
 
   // Discard trailing style element if there is no output to apply it to.
   if (aStyles.Length() > aSequence.Length()) {
     aStyles.TruncateLength(aSequence.Length());
   }
 
   // The rest of the array, if unused by the format string.
   for (; index < aData.Length(); ++index) {
-    if (!aSequence.AppendElement(aData[index], fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
       return false;
     }
   }
 
   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);
@@ -1808,18 +1964,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) {
@@ -1841,28 +1995,28 @@ Console::StartTimer(JSContext* aCx, cons
                     nsAString& aTimerLabel,
                     DOMHighResTimeStamp* aTimerValue)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aTimerValue);
 
   *aTimerValue = 0;
 
-  if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
+  if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
     return false;
   }
 
   JS::Rooted<JS::Value> name(aCx, aName);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString label;
-  if (!label.init(aCx, jsString)) {
+  if (NS_WARN_IF(!label.init(aCx, jsString))) {
     return false;
   }
 
   DOMHighResTimeStamp entry;
   if (!mTimerRegistry.Get(label, &entry)) {
     mTimerRegistry.Put(label, aTimestamp);
   } else {
     aTimestamp = entry;
@@ -1873,18 +2027,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();
     }
 
@@ -1912,43 +2064,41 @@ Console::StopTimer(JSContext* aCx, const
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aTimerDuration);
 
   *aTimerDuration = 0;
 
   JS::Rooted<JS::Value> name(aCx, aName);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString key;
-  if (!key.init(aCx, jsString)) {
+  if (NS_WARN_IF(!key.init(aCx, jsString))) {
     return false;
   }
 
   DOMHighResTimeStamp entry;
-  if (!mTimerRegistry.Get(key, &entry)) {
+  if (NS_WARN_IF(!mTimerRegistry.Get(key, &entry))) {
     return false;
   }
 
   mTimerRegistry.Remove(key);
 
   aTimerLabel = key;
   *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;
 
@@ -1959,20 +2109,18 @@ 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 (!aSequence.AppendElement(aData[i], fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
       return false;
     }
   }
 
   return true;
 }
 
 uint32_t
@@ -2016,18 +2164,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();
@@ -2079,31 +2225,129 @@ 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::UnstoreCallData(ConsoleCallData* aCallData)
+{
+  AssertIsOnOwningThread();
+  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::UnregisterConsoleCallData(ConsoleCallData* aData)
+Console::NotifyHandler(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+                       const Sequence<JS::Value>& aArguments,
+                       ConsoleCallData* aCallData) const
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aCallData);
+
+  if (!mConsoleEventHandler) {
+    return;
+  }
+
+  JSAutoCompartment ac(aCx, mConsoleEventHandler->Callable());
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (NS_WARN_IF(!PopulateEvent(aCx, mConsoleEventHandler->Callable(),
+                                aArguments, &value, aCallData))) {
+    return;
+  }
+
+  ErrorResult rv;
+  JS::Rooted<JS::Value> ignored(aCx);
+  mConsoleEventHandler->Call(value, &ignored, rv);
+  rv.SuppressException();
+}
+
+void
+Console::RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                               ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  MOZ_ASSERT(mConsoleCallDataArray.Contains(aData));
-  mConsoleCallDataArray.RemoveElement(aData);
+  // We don't want to expose this functionality to main-thread yet.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+
+  for (uint32_t i = 0; i < mCallDataStorage.Length(); ++i) {
+    JS::Rooted<JS::Value> value(aCx);
+
+    Sequence<JS::Value> sequence;
+    if (!mCallDataStorage[i]->PopulateSequenceArguments(sequence)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    SequenceRooter<JS::Value> arguments(aCx, &sequence);
+    if (NS_WARN_IF(!PopulateEvent(aCx, global, sequence, &value,
+                                  mCallDataStorage[i]))) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+
+    aEvents.AppendElement(value);
+  }
+}
+
+void
+Console::SetConsoleEventHandler(AnyCallback& aHandler)
+{
+  AssertIsOnOwningThread();
+
+  // We don't want to expose this functionality to main-thread yet.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  mConsoleEventHandler = &aHandler;
 }
 
 void
 Console::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mOwningThread);
   MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
 }
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -20,16 +20,17 @@
 #include "nsPIDOMWindow.h"
 
 class nsIConsoleAPIStorage;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
+class AnyCallback;
 class ConsoleCallData;
 class ConsoleRunnable;
 class ConsoleCallDataRunnable;
 class ConsoleProfileRunnable;
 struct ConsoleStackEntry;
 
 class Console final : public nsIObserver
                     , public nsWrapperCache
@@ -37,17 +38,18 @@ class Console final : public nsIObserver
 {
   ~Console();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console, nsIObserver)
   NS_DECL_NSIOBSERVER
 
-  explicit Console(nsPIDOMWindowInner* aWindow);
+  static already_AddRefed<Console>
+  Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv);
 
   // WebIDL methods
   nsPIDOMWindowInner* GetParentObject() const
   {
     return mWindow;
   }
 
   virtual JSObject*
@@ -111,17 +113,35 @@ public:
   Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
 
   void
   Count(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
   NoopMethod();
 
+  void
+  RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                        ErrorResult& aRv);
+
+  void
+  SetConsoleEventHandler(AnyCallback& aHandler);
+
+  void
+  ClearStorage();
+
+  void
+  Shutdown();
+
 private:
+  explicit Console(nsPIDOMWindowInner* aWindow);
+
+  void
+  Initialize(ErrorResult& aRv);
+
   enum MethodName
   {
     MethodLog,
     MethodInfo,
     MethodWarn,
     MethodError,
     MethodException,
     MethodDebug,
@@ -143,16 +163,39 @@ 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);
+
+  void
+  NotifyHandler(JSContext* aCx,
+                JS::Handle<JSObject*> aGlobal,
+                const Sequence<JS::Value>& aArguments,
+                ConsoleCallData* aData) const;
+
+  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
@@ -194,51 +237,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;
@@ -248,74 +290,85 @@ 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;
+
+  RefPtr<AnyCallback> mConsoleEventHandler;
 
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
   uint64_t mOuterID;
   uint64_t mInnerID;
 
+  enum {
+    eUnknown,
+    eInitialized,
+    eShuttingDown
+  } mStatus;
+
   friend class ConsoleCallData;
   friend class ConsoleRunnable;
   friend class ConsoleCallDataRunnable;
   friend class ConsoleProfileRunnable;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13756,17 +13756,20 @@ nsGlobalWindow::Orientation() const
 #endif
 
 Console*
 nsGlobalWindow::GetConsole(ErrorResult& aRv)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   if (!mConsole) {
-    mConsole = new Console(AsInner());
+    mConsole = Console::Create(AsInner(), aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
   }
 
   return mConsole;
 }
 
 already_AddRefed<External>
 nsGlobalWindow::GetExternal(ErrorResult& aRv)
 {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1597,31 +1597,30 @@ DOMInterfaces = {
         'terminate',
     ],
 },
 
 'WorkerDebuggerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'nativeType': 'mozilla::dom::workers::WorkerDebuggerGlobalScope',
     'implicitJSContext': [
-        'dump', 'global', 'reportError',
+        'dump', 'global', 'reportError', 'setConsoleEventHandler',
     ],
 },
 
 'WorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'workers': True,
     'concrete': False,
     'implicitJSContext': [
         'close',
     ],
     # Rename a few things so we don't have both classes and methods
     # with the same name
     'binaryNames': {
-        'console': 'getConsole',
         'performance': 'getPerformance',
     },
 },
 
 'WorkerLocation': {
     'headerFile': 'mozilla/dom/workers/bindings/Location.h',
     'workers': True,
 },
--- a/dom/webidl/WorkerDebuggerGlobalScope.webidl
+++ b/dom/webidl/WorkerDebuggerGlobalScope.webidl
@@ -19,16 +19,22 @@ interface WorkerDebuggerGlobalScope : Ev
   void postMessage(DOMString message);
 
   attribute EventHandler onmessage;
 
   [Throws]
   void setImmediate(Function handler);
 
   void reportError(DOMString message);
+
+  [Throws]
+  sequence<any> retrieveConsoleEvents();
+
+  [Throws]
+  void setConsoleEventHandler(AnyCallback handler);
 };
 
 // So you can debug while you debug
 partial interface WorkerDebuggerGlobalScope {
   void dump(optional DOMString string);
 
   [Throws, Replaceable]
   readonly attribute Console console;
--- a/dom/webidl/WorkerGlobalScope.webidl
+++ b/dom/webidl/WorkerGlobalScope.webidl
@@ -12,17 +12,17 @@
  * this document.
  */
 
 [Exposed=(Worker)]
 interface WorkerGlobalScope : EventTarget {
   [Constant, Cached]
   readonly attribute WorkerGlobalScope self;
 
-  [Replaceable]
+  [Throws, Replaceable]
   readonly attribute Console console;
 
   readonly attribute WorkerLocation location;
 
   [Throws]
   void close();
   attribute OnErrorEventHandler onerror;
 
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2445,16 +2445,22 @@ RuntimeService::CycleCollectAllWorkers()
 }
 
 void
 RuntimeService::SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline)
 {
   BROADCAST_ALL_WORKERS(OfflineStatusChangeEvent, aIsOffline);
 }
 
+void
+RuntimeService::MemoryPressureAllWorkers()
+{
+  BROADCAST_ALL_WORKERS(MemoryPressure, /* dummy = */ false);
+}
+
 // nsISupports
 NS_IMPL_ISUPPORTS(RuntimeService, nsIObserver)
 
 // nsIObserver
 NS_IMETHODIMP
 RuntimeService::Observe(nsISupports* aSubject, const char* aTopic,
                         const char16_t* aData)
 {
@@ -2473,16 +2479,17 @@ RuntimeService::Observe(nsISupports* aSu
     return NS_OK;
   }
   if (!strcmp(aTopic, CC_REQUEST_OBSERVER_TOPIC)) {
     CycleCollectAllWorkers();
     return NS_OK;
   }
   if (!strcmp(aTopic, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
     GarbageCollectAllWorkers(/* shrinking = */ true);
+    MemoryPressureAllWorkers();
     CycleCollectAllWorkers();
     return NS_OK;
   }
   if (!strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
     SendOfflineStatusChangeEventToAllWorkers(NS_IsOffline());
     return NS_OK;
   }
   if (!strcmp(aTopic, NS_IOSERVICE_APP_OFFLINE_STATUS_TOPIC)) {
--- a/dom/workers/RuntimeService.h
+++ b/dom/workers/RuntimeService.h
@@ -244,16 +244,19 @@ public:
   GarbageCollectAllWorkers(bool aShrinking);
 
   void
   CycleCollectAllWorkers();
 
   void
   SendOfflineStatusChangeEventToAllWorkers(bool aIsOffline);
 
+  void
+  MemoryPressureAllWorkers();
+
 private:
   RuntimeService();
   ~RuntimeService();
 
   nsresult
   Init();
 
   void
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -39,16 +39,17 @@
 #include "js/MemoryMetrics.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Console.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
@@ -1533,16 +1534,31 @@ public:
     aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
     return true;
   }
 
 private:
   bool mIsOffline;
 };
 
+class MemoryPressureRunnable : public WorkerControlRunnable
+{
+public:
+  explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
+    : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+  {}
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
+  {
+    aWorkerPrivate->MemoryPressureInternal();
+    return true;
+  }
+};
+
 #ifdef DEBUG
 static bool
 StartsWithExplicit(nsACString& s)
 {
     return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/"));
 }
 #endif
 
@@ -3102,16 +3118,27 @@ WorkerPrivate::OfflineStatusChangeEventI
 
   event->InitEvent(eventType, false, false);
   event->SetTrusted(true);
 
   globalScope->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
 }
 
 template <class Derived>
+void
+WorkerPrivateParent<Derived>::MemoryPressure(bool aDummy)
+{
+  AssertIsOnParentThread();
+
+  RefPtr<MemoryPressureRunnable> runnable =
+    new MemoryPressureRunnable(ParentAsWorkerPrivate());
+  NS_WARN_IF(!runnable->Dispatch());
+}
+
+template <class Derived>
 bool
 WorkerPrivateParent<Derived>::RegisterSharedWorker(SharedWorker* aSharedWorker,
                                                    MessagePort* aPort)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aSharedWorker);
   MOZ_ASSERT(IsSharedWorker());
   MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker));
@@ -6267,16 +6294,31 @@ WorkerPrivate::CycleCollectInternal(bool
   if (aCollectChildren) {
     for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
       mChildWorkers[index]->CycleCollect(/* dummy = */ false);
     }
   }
 }
 
 void
+WorkerPrivate::MemoryPressureInternal()
+{
+  AssertIsOnWorkerThread();
+
+  RefPtr<Console> console = mScope ? mScope->GetConsoleIfExists() : nullptr;
+  if (console) {
+    console->ClearStorage();
+  }
+
+  for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
+    mChildWorkers[index]->MemoryPressure(false);
+  }
+}
+
+void
 WorkerPrivate::SetThread(WorkerThread* aThread)
 {
   if (aThread) {
 #ifdef DEBUG
     {
       bool isOnCurrentThread;
       MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
       MOZ_ASSERT(isOnCurrentThread);
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -368,16 +368,19 @@ public:
   GarbageCollect(bool aShrinking);
 
   void
   CycleCollect(bool aDummy);
 
   void
   OfflineStatusChangeEvent(bool aIsOffline);
 
+  void
+  MemoryPressure(bool aDummy);
+
   bool
   RegisterSharedWorker(SharedWorker* aSharedWorker, MessagePort* aPort);
 
   void
   BroadcastErrorToSharedWorkers(JSContext* aCx,
                                 const nsAString& aMessage,
                                 const nsAString& aFilename,
                                 const nsAString& aLine,
@@ -1189,16 +1192,19 @@ public:
                          bool aCollectChildren);
 
   void
   CycleCollectInternal(bool aCollectChildren);
 
   void
   OfflineStatusChangeEventInternal(bool aIsOffline);
 
+  void
+  MemoryPressureInternal();
+
   JSContext*
   GetJSContext() const
   {
     AssertIsOnWorkerThread();
     return mJSContext;
   }
 
   WorkerGlobalScope*
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -112,22 +112,25 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEvent
 
 JSObject*
 WorkerGlobalScope::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   MOZ_CRASH("We should never get here!");
 }
 
 Console*
-WorkerGlobalScope::GetConsole()
+WorkerGlobalScope::GetConsole(ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (!mConsole) {
-    mConsole = new Console(nullptr);
+    mConsole = Console::Create(nullptr, aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
   }
 
   return mConsole;
 }
 
 already_AddRefed<CacheStorage>
 WorkerGlobalScope::GetCaches(ErrorResult& aRv)
 {
@@ -909,24 +912,55 @@ WorkerDebuggerGlobalScope::ReportError(J
 {
   JS::UniqueChars chars;
   uint32_t lineno = 0;
   JS::DescribeScriptedCaller(aCx, &chars, &lineno);
   nsString filename(NS_ConvertUTF8toUTF16(chars.get()));
   mWorkerPrivate->ReportErrorToDebugger(filename, lineno, aMessage);
 }
 
+void
+WorkerDebuggerGlobalScope::RetrieveConsoleEvents(JSContext* aCx,
+                                                 nsTArray<JS::Value>& aEvents,
+                                                 ErrorResult& aRv)
+{
+  RefPtr<Console> console =
+    mWorkerPrivate->GetOrCreateGlobalScope(aCx)->GetConsole(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  console->RetrieveConsoleEvents(aCx, aEvents, aRv);
+}
+
+void
+WorkerDebuggerGlobalScope::SetConsoleEventHandler(JSContext* aCx,
+                                                  AnyCallback& aHandler,
+                                                  ErrorResult& aRv)
+{
+  RefPtr<Console> console =
+    mWorkerPrivate->GetOrCreateGlobalScope(aCx)->GetConsole(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  console->SetConsoleEventHandler(aHandler);
+}
+
 Console*
 WorkerDebuggerGlobalScope::GetConsole(ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   // Debugger console has its own console object.
   if (!mConsole) {
-    mConsole = new Console(nullptr);
+    mConsole = Console::Create(nullptr, aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
   }
 
   return mConsole;
 }
 
 void
 WorkerDebuggerGlobalScope::Dump(JSContext* aCx,
                                 const Optional<nsAString>& aString) const
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -12,16 +12,17 @@
 #include "mozilla/dom/Headers.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "nsWeakReference.h"
 #include "mozilla/dom/ImageBitmapSource.h"
 
 namespace mozilla {
 namespace dom {
 
+class AnyCallback;
 class Console;
 class Function;
 class IDBFactory;
 class Promise;
 class RequestOrUSVString;
 class ServiceWorkerRegistrationWorkerThread;
 
 namespace cache {
@@ -80,17 +81,23 @@ public:
 
   WorkerGlobalScope*
   Self()
   {
     return this;
   }
 
   Console*
-  GetConsole();
+  GetConsole(ErrorResult& aRv);
+
+  Console*
+  GetConsoleIfExists() const
+  {
+    return mConsole;
+  }
 
   already_AddRefed<WorkerLocation>
   Location();
 
   already_AddRefed<WorkerNavigator>
   Navigator();
 
   already_AddRefed<WorkerNavigator>
@@ -321,16 +328,24 @@ public:
   IMPL_EVENT_HANDLER(message)
 
   void
   SetImmediate(Function& aHandler, ErrorResult& aRv);
 
   void
   ReportError(JSContext* aCx, const nsAString& aMessage);
 
+  void
+  RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                        ErrorResult& aRv);
+
+  void
+  SetConsoleEventHandler(JSContext* aCx, AnyCallback& aHandler,
+                         ErrorResult& aRv);
+
   Console*
   GetConsole(ErrorResult& aRv);
 
   void
   Dump(JSContext* aCx, const Optional<nsAString>& aString) const;
 
 private:
   virtual ~WorkerDebuggerGlobalScope();
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_childWorker.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_debugger.js
@@ -0,0 +1,37 @@
+"use strict"
+
+function ok(a, msg) {
+  postMessage(JSON.stringify({ type: 'status', what: !!a, msg: msg }));
+}
+
+function is(a, b, msg) {
+  ok(a === b, msg);
+}
+
+function finish() {
+  postMessage(JSON.stringify({ type: 'finish' }));
+}
+
+function magic() {
+  console.log("Hello from the debugger script!");
+
+  var foo = retrieveConsoleEvents();
+  ok(Array.isArray(foo), "We received an array.");
+  ok(foo.length >= 2, "At least 2 messages.");
+
+  is(foo[0].arguments[0], "Can you see this console message?", "First message ok.");
+  is(foo[1].arguments[0], "Can you see this second console message?", "Second message ok.");
+
+  setConsoleEventHandler(function(consoleData) {
+    is(consoleData.arguments[0], "Random message.", "Random message ok!");
+    finish();
+  });
+}
+
+this.onmessage = function (event) {
+  switch (event.data) {
+  case "do magic":
+    magic();
+    break;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/WorkerDebugger.console_worker.js
@@ -0,0 +1,10 @@
+"use strict";
+
+console.log("Can you see this console message?");
+console.warn("Can you see this second console message?");
+
+var worker = new Worker("WorkerDebugger.console_childWorker.js");
+
+setInterval(function() {
+  console.log("Random message.");
+}, 200);
--- a/dom/workers/test/chrome.ini
+++ b/dom/workers/test/chrome.ini
@@ -1,11 +1,14 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
+  WorkerDebugger.console_childWorker.js
+  WorkerDebugger.console_debugger.js
+  WorkerDebugger.console_worker.js
   WorkerDebugger.initialize_childWorker.js
   WorkerDebugger.initialize_debugger.js
   WorkerDebugger.initialize_worker.js
   WorkerDebugger.postMessage_childWorker.js
   WorkerDebugger.postMessage_debugger.js
   WorkerDebugger.postMessage_worker.js
   WorkerDebugger_frozen_iframe1.html
   WorkerDebugger_frozen_iframe2.html
@@ -57,16 +60,17 @@ support-files =
 [test_WorkerDebugger.initialize.xul]
 [test_WorkerDebugger.postMessage.xul]
 [test_WorkerDebugger.xul]
 [test_WorkerDebuggerGlobalScope.createSandbox.xul]
 [test_WorkerDebuggerGlobalScope.enterEventLoop.xul]
 [test_WorkerDebuggerGlobalScope.reportError.xul]
 [test_WorkerDebuggerGlobalScope.setImmediate.xul]
 [test_WorkerDebuggerManager.xul]
+[test_WorkerDebugger_console.xul]
 [test_WorkerDebugger_frozen.xul]
 [test_WorkerDebugger_suspended.xul]
 [test_bug883784.xul]
 [test_chromeWorker.xul]
 [test_chromeWorkerJSM.xul]
 [test_extension.xul]
 [test_extensionBootstrap.xul]
 [test_file.xul]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_WorkerDebugger_console.xul
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for WorkerDebuggerGlobalScope.console methods"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+  <script type="application/javascript" src="dom_worker_helper.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+
+    const WORKER_URL = "WorkerDebugger.console_worker.js";
+    const CHILD_WORKER_URL = "WorkerDebugger.console_childWorker.js";
+    const DEBUGGER_URL = BASE_URL + "WorkerDebugger.console_debugger.js";
+
+    consoleMessagesReceived = 0;
+    function test() {
+      function consoleListener() {
+        SpecialPowers.addObserver(this, "console-api-log-event", false);
+      }
+
+      consoleListener.prototype  = {
+        observe: function(aSubject, aTopic, aData) {
+          if (aTopic == "console-api-log-event") {
+            var obj = aSubject.wrappedJSObject;
+            if (obj.arguments[0] == "Hello from the debugger script!" &&
+                !consoleMessagesReceived) {
+              consoleMessagesReceived++;
+              ok(true, "Something has been received");
+              SpecialPowers.removeObserver(this, "console-api-log-event");
+            }
+          }
+        }
+      }
+
+      var cl = new consoleListener();
+
+      Task.spawn(function* () {
+        SimpleTest.waitForExplicitFinish();
+
+        info("Create a worker that creates a child worker, wait for their " +
+             "debuggers to be registered, and initialize them.");
+        let promise = waitForMultiple([
+          waitForRegister(WORKER_URL, DEBUGGER_URL),
+          waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL)
+        ]);
+        let worker = new Worker(WORKER_URL);
+        let [dbg, childDbg] = yield promise;
+
+        info("Send a request to the worker debugger. This should cause the " +
+             "the worker debugger to send a response.");
+        dbg.addListener({
+          onMessage: function(msg) {
+            try {
+              msg = JSON.parse(msg);
+            } catch(e) {
+              ok(false, "Something went wrong");
+              return;
+            }
+
+            if (msg.type == 'finish') {
+              ok(consoleMessagesReceived, "We received something via debugger console!");
+              dbg.removeListener(this);
+              SimpleTest.finish();
+              return;
+            }
+
+            if (msg.type == 'status') {
+              ok(msg.what, msg.msg);
+              return;
+            }
+
+            ok(false, "Something went wrong");
+          }
+        });
+
+        dbg.postMessage("do magic");
+      });
+    }
+
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+  </body>
+  <label id="test-result"/>
+</window>