Bug 1058644 - Console API in ServiceWorkers. r=khuey
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 06 Jan 2015 10:45:00 -0500
changeset 222288 d32672ecbff9
parent 222287 bb194bedf519
child 222289 8df0524197aa
push id53576
push userryanvm@gmail.com
push dateTue, 06 Jan 2015 21:36:53 +0000
treeherdermozilla-inbound@ba7511d524d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1058644
milestone37.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 1058644 - Console API in ServiceWorkers. r=khuey
dom/base/Console.cpp
dom/base/Console.h
dom/base/ConsoleAPIStorage.js
dom/base/nsIConsoleAPIStorage.idl
dom/workers/test/mochitest.ini
dom/workers/test/sharedWorker_console.js
dom/workers/test/test_consoleSharedWorkers.html
toolkit/devtools/Console.jsm
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -24,34 +24,28 @@
 
 #include "nsIConsoleAPIStorage.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsIServiceManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWebNavigation.h"
+#include "nsIXPConnect.h"
 
 // The maximum allowed number of concurrent timers per page.
 #define MAX_PAGE_TIMERS 10000
 
 // The maximum allowed number of concurrent counters per page.
 #define MAX_PAGE_COUNTERS 10000
 
 // The maximum stacktrace depth when populating the stacktrace array used for
 // console.trace().
 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
 
-// The console API methods are async and their action is executed later. This
-// delay tells how much later.
-#define CALL_DELAY 15 // milliseconds
-
-// This constant tells how many messages to process in a single timer execution.
-#define MESSAGES_IN_INTERVAL 1500
-
 // This tag is used in the Structured Clone Algorithm to move js values from
 // worker thread to main thread
 #define CONSOLE_TAG JS_SCTAG_USER_MIN
 
 using namespace mozilla::dom::exceptions;
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
@@ -132,53 +126,93 @@ ConsoleStructuredCloneCallbacksError(JSC
 }
 
 static const JSStructuredCloneCallbacks gConsoleCallbacks = {
   ConsoleStructuredCloneCallbacksRead,
   ConsoleStructuredCloneCallbacksWrite,
   ConsoleStructuredCloneCallbacksError
 };
 
-class ConsoleCallData MOZ_FINAL : public LinkedListElement<ConsoleCallData>
+class ConsoleCallData MOZ_FINAL
 {
 public:
   ConsoleCallData()
     : mMethodName(Console::MethodLog)
     , mPrivate(false)
     , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
-    , mMonotonicTimer(0)
-  {
-    MOZ_COUNT_CTOR(ConsoleCallData);
-  }
-
-  ~ConsoleCallData()
-  {
-    MOZ_COUNT_DTOR(ConsoleCallData);
-  }
+    , mIDType(eUnknown)
+    , mOuterIDNumber(0)
+    , mInnerIDNumber(0)
+  { }
 
   void
   Initialize(JSContext* aCx, Console::MethodName aName,
              const nsAString& aString, const Sequence<JS::Value>& aArguments)
   {
     mGlobal = JS::CurrentGlobalOrNull(aCx);
     mMethodName = aName;
     mMethodString = aString;
 
     for (uint32_t i = 0; i < aArguments.Length(); ++i) {
       mArguments.AppendElement(aArguments[i]);
     }
   }
 
+  void
+  SetIDs(uint64_t aOuterID, uint64_t aInnerID)
+  {
+    MOZ_ASSERT(mIDType == eUnknown);
+
+    mOuterIDNumber = aOuterID;
+    mInnerIDNumber = aInnerID;
+    mIDType = eNumber;
+  }
+
+  void
+  SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
+  {
+    MOZ_ASSERT(mIDType == eUnknown);
+
+    mOuterIDString = aOuterID;
+    mInnerIDString = aInnerID;
+    mIDType = eString;
+  }
+
+  void
+  CleanupJSObjects()
+  {
+    mArguments.Clear();
+    mGlobal = nullptr;
+  }
+
   JS::Heap<JSObject*> mGlobal;
 
   Console::MethodName mMethodName;
   bool mPrivate;
   int64_t mTimeStamp;
   DOMHighResTimeStamp mMonotonicTimer;
 
+  // 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.
+  // In Console.jsm the ID is 'jsm'.
+  enum {
+    eString,
+    eNumber,
+    eUnknown
+  } mIDType;
+
+  uint64_t mOuterIDNumber;
+  nsString mOuterIDString;
+
+  uint64_t mInnerIDNumber;
+  nsString mInnerIDString;
+
   nsString mMethodString;
   nsTArray<JS::Heap<JS::Value>> mArguments;
 
   // Stack management is complicated, because we want to do it as
   // lazily as possible.  Therefore, we have the following behavior:
   // 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
@@ -204,18 +238,19 @@ public:
 
 private:
   JSContext* mCx;
 };
 
 class ConsoleRunnable : public nsRunnable
 {
 public:
-  ConsoleRunnable()
+  ConsoleRunnable(Console* aConsole)
     : mWorkerPrivate(GetCurrentThreadWorkerPrivate())
+    , mConsole(aConsole)
   {
     MOZ_ASSERT(mWorkerPrivate);
   }
 
   virtual
   ~ConsoleRunnable()
   {
   }
@@ -243,56 +278,128 @@ public:
     return syncLoop.Run();
   }
 
 private:
   NS_IMETHOD Run()
   {
     AssertIsOnMainThread();
 
-    RunConsole();
+    // Walk up to our containing page
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    nsPIDOMWindow* window = wp->GetWindow();
+    if (!window) {
+      RunWindowless();
+    } else {
+      RunWithWindow(window);
+    }
 
     nsRefPtr<MainThreadStopSyncLoopRunnable> response =
       new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
                                          mSyncLoopTarget.forget(),
                                          true);
     if (!response->Dispatch(nullptr)) {
       NS_WARNING("Failed to dispatch response!");
     }
 
     return NS_OK;
   }
 
+  void
+  RunWithWindow(nsPIDOMWindow* aWindow)
+  {
+    AutoJSAPI jsapi;
+    MOZ_ASSERT(aWindow);
+
+    nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
+    if (NS_WARN_IF(!jsapi.Init(win))) {
+      return;
+    }
+
+    MOZ_ASSERT(aWindow->IsInnerWindow());
+    nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
+    MOZ_ASSERT(outerWindow);
+
+    RunConsole(jsapi.cx(), outerWindow, aWindow);
+  }
+
+  void
+  RunWindowless()
+  {
+    WorkerPrivate* wp = mWorkerPrivate;
+    while (wp->GetParent()) {
+      wp = wp->GetParent();
+    }
+
+    MOZ_ASSERT(!wp->GetWindow());
+
+    AutoSafeJSContext cx;
+
+    nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox =
+      mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal());
+    if (NS_WARN_IF(!sandbox)) {
+       return;
+    }
+
+    JS::Rooted<JSObject*> global(cx, sandbox->GetJSObject());
+    if (NS_WARN_IF(!global)) {
+      return;
+    }
+
+    // The CreateSandbox call returns a proxy to the actual sandbox object. We
+    // don't need a proxy here.
+    global = js::UncheckedUnwrap(global);
+
+    JSAutoCompartment ac(cx, global);
+
+    RunConsole(cx, nullptr, nullptr);
+  }
+
 protected:
   virtual bool
   PreDispatch(JSContext* aCx) = 0;
 
   virtual void
-  RunConsole() = 0;
+  RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
+             nsPIDOMWindow* aInnerWindow) = 0;
 
   WorkerPrivate* mWorkerPrivate;
 
+  // Raw pointer because this method is async and this object is kept alive by
+  // the caller.
+  Console* mConsole;
+
 private:
   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
 };
 
 // This runnable appends a CallData object into the Console queue running on
 // the main-thread.
 class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
 {
 public:
-  explicit ConsoleCallDataRunnable(ConsoleCallData* aCallData)
-    : mCallData(aCallData)
-  {
-  }
+  ConsoleCallDataRunnable(Console* aConsole,
+                          ConsoleCallData* aCallData)
+    : ConsoleRunnable(aConsole)
+    , mCallData(aCallData)
+  { }
 
 private:
+  ~ConsoleCallDataRunnable()
+  { }
+
   bool
   PreDispatch(JSContext* aCx) MOZ_OVERRIDE
   {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
     ClearException ce(aCx);
     JSAutoCompartment ac(aCx, mCallData->mGlobal);
 
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mCallData->mArguments.Length()));
     if (!arguments) {
       return false;
     }
@@ -306,96 +413,106 @@ private:
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
     if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
       return false;
     }
 
-    mCallData->mArguments.Clear();
-    mCallData->mGlobal = nullptr;
+    mCallData->CleanupJSObjects();
     return true;
   }
 
   void
-  RunConsole() MOZ_OVERRIDE
+  RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
+             nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
   {
-    // Walk up to our containing page
-    WorkerPrivate* wp = mWorkerPrivate;
-    while (wp->GetParent()) {
-      wp = wp->GetParent();
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // The windows have to run in parallel.
+    MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
+
+    if (aOuterWindow) {
+      mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
+    } else {
+      ConsoleStackEntry frame;
+      if (mCallData->mTopStackFrame) {
+        frame = *mCallData->mTopStackFrame;
+      }
+
+      nsString id;
+      if (mWorkerPrivate->IsSharedWorker()) {
+        id = NS_LITERAL_STRING("SharedWorker");
+      } else if (mWorkerPrivate->IsServiceWorker()) {
+        id = NS_LITERAL_STRING("ServiceWorker");
+      } else {
+        id = NS_LITERAL_STRING("Worker");
+      }
+
+      mCallData->SetIDs(id, frame.mFilename);
     }
 
-    nsPIDOMWindow* window = wp->GetWindow();
-    NS_ENSURE_TRUE_VOID(window);
-
-    nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
-    NS_ENSURE_TRUE_VOID(win);
+    ProcessCallData(aCx);
+    mCallData->CleanupJSObjects();
+  }
 
-    AutoJSAPI jsapi;
-    if (NS_WARN_IF(!jsapi.Init(win))) {
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-    ClearException ce(cx);
+private:
+  void
+  ProcessCallData(JSContext* aCx)
+  {
+    ClearException ce(aCx);
 
-    ErrorResult error;
-    nsRefPtr<Console> console = win->GetConsole(error);
-    if (error.Failed()) {
-      NS_WARNING("Failed to get console from the window.");
-      return;
-    }
-
-    JS::Rooted<JS::Value> argumentsValue(cx);
-    if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
+    JS::Rooted<JS::Value> argumentsValue(aCx);
+    if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
-    JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
-    MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
+    JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
+    MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
-    if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
+    if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
       return;
     }
 
     for (uint32_t i = 0; i < length; ++i) {
-      JS::Rooted<JS::Value> value(cx);
+      JS::Rooted<JS::Value> value(aCx);
 
-      if (!JS_GetElement(cx, argumentsObj, i, &value)) {
+      if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
         return;
       }
 
       mCallData->mArguments.AppendElement(value);
     }
 
     MOZ_ASSERT(mCallData->mArguments.Length() == length);
 
-    mCallData->mGlobal = JS::CurrentGlobalOrNull(cx);
-    console->AppendCallData(mCallData.forget());
+    mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
+    mConsole->ProcessCallData(mCallData);
   }
 
-private:
-  nsAutoPtr<ConsoleCallData> mCallData;
+  ConsoleCallData* mCallData;
 
   JSAutoStructuredCloneBuffer mArguments;
   nsTArray<nsString> mStrings;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
 {
 public:
-  ConsoleProfileRunnable(const nsAString& aAction,
+  ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
                          const Sequence<JS::Value>& aArguments)
-    : mAction(aAction)
+    : ConsoleRunnable(aConsole)
+    , mAction(aAction)
     , mArguments(aArguments)
   {
+    MOZ_ASSERT(aConsole);
   }
 
 private:
   bool
   PreDispatch(JSContext* aCx) MOZ_OVERRIDE
   {
     ClearException ce(aCx);
 
@@ -425,124 +542,84 @@ private:
     if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
       return false;
     }
 
     return true;
   }
 
   void
-  RunConsole() MOZ_OVERRIDE
+  RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
+             nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
   {
-    // Walk up to our containing page
-    WorkerPrivate* wp = mWorkerPrivate;
-    while (wp->GetParent()) {
-      wp = wp->GetParent();
-    }
-
-    nsPIDOMWindow* window = wp->GetWindow();
-    NS_ENSURE_TRUE_VOID(window);
-
-    nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
-    NS_ENSURE_TRUE_VOID(win);
+    ClearException ce(aCx);
 
-    AutoJSAPI jsapi;
-    if (NS_WARN_IF(!jsapi.Init(win))) {
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-    ClearException ce(cx);
-
-    ErrorResult error;
-    nsRefPtr<Console> console = win->GetConsole(error);
-    if (error.Failed()) {
-      NS_WARNING("Failed to get console from the window.");
-      return;
-    }
-
-    JS::Rooted<JS::Value> argumentsValue(cx);
-    if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
+    JS::Rooted<JS::Value> argumentsValue(aCx);
+    if (!mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
       return;
     }
 
     MOZ_ASSERT(argumentsValue.isObject());
-    JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
-    MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
+    JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
+    MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
 
     uint32_t length;
-    if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
+    if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
       return;
     }
 
     Sequence<JS::Value> arguments;
 
     for (uint32_t i = 0; i < length; ++i) {
-      JS::Rooted<JS::Value> value(cx);
+      JS::Rooted<JS::Value> value(aCx);
 
-      if (!JS_GetElement(cx, argumentsObj, i, &value)) {
+      if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
         return;
       }
 
       arguments.AppendElement(value);
     }
 
-    console->ProfileMethod(cx, mAction, arguments);
+    mConsole->ProfileMethod(aCx, mAction, arguments);
   }
 
-private:
   nsString mAction;
   Sequence<JS::Value> mArguments;
 
   JSAutoStructuredCloneBuffer mBuffer;
   nsTArray<nsString> mStrings;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
+// We don't need to traverse/unlink mStorage and mSanbox 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(mTimer)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
-
-  tmp->ClearConsoleData();
-
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-
-  for (ConsoleCallData* data = tmp->mQueuedCalls.getFirst(); data != nullptr;
-       data = data->getNext()) {
-    if (data->mGlobal) {
-      aCallbacks.Trace(&data->mGlobal, "data->mGlobal", aClosure);
-    }
-
-    for (uint32_t i = 0; i < data->mArguments.Length(); ++i) {
-      aCallbacks.Trace(&data->mArguments[i], "data->mArguments[i]", aClosure);
-    }
-  }
-
 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(nsITimerCallback)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Console::Console(nsPIDOMWindow* aWindow)
   : mWindow(aWindow)
   , mOuterID(0)
   , mInnerID(0)
 {
   if (mWindow) {
@@ -561,16 +638,33 @@ Console::Console(nsPIDOMWindow* aWindow)
     }
   }
 
   mozilla::HoldJSObjects(this);
 }
 
 Console::~Console()
 {
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIThread> mainThread;
+    NS_GetMainThread(getter_AddRefs(mainThread));
+
+    if (mStorage) {
+      nsIConsoleAPIStorage* storage;
+      mStorage.forget(&storage);
+      NS_ProxyRelease(mainThread, storage, false);
+    }
+
+    if (mSandbox) {
+      nsIXPConnectJSObjectHolder* sandbox;
+      mSandbox.forget(&sandbox);
+      NS_ProxyRelease(mainThread, sandbox, false);
+    }
+  }
+
   mozilla::DropJSObjects(this);
 }
 
 NS_IMETHODIMP
 Console::Observe(nsISupports* aSubject, const char* aTopic,
                  const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -588,23 +682,17 @@ Console::Observe(nsISupports* aSubject, 
 
   if (innerID == mInnerID) {
     nsCOMPtr<nsIObserverService> obs =
       do_GetService("@mozilla.org/observer-service;1");
     if (obs) {
       obs->RemoveObserver(this, "inner-window-destroyed");
     }
 
-    ClearConsoleData();
     mTimerRegistry.Clear();
-
-    if (mTimer) {
-      mTimer->Cancel();
-      mTimer = nullptr;
-    }
   }
 
   return NS_OK;
 }
 
 JSObject*
 Console::WrapObject(JSContext* aCx)
 {
@@ -680,17 +768,17 @@ Console::ProfileEnd(JSContext* aCx, cons
 
 void
 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
                        const Sequence<JS::Value>& aData)
 {
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     nsRefPtr<ConsoleProfileRunnable> runnable =
-      new ConsoleProfileRunnable(aAction, aData);
+      new ConsoleProfileRunnable(this, aAction, aData);
     runnable->Dispatch();
     return;
   }
 
   ClearException ce(aCx);
 
   RootedDictionary<ConsoleProfileEvent> event(aCx);
   event.mAction = aAction;
@@ -838,53 +926,21 @@ public:
 };
 
 // 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)
 {
-  // This RAII class removes the last element of the mQueuedCalls if something
-  // goes wrong.
-  class RAII {
-  public:
-    explicit RAII(LinkedList<ConsoleCallData>& aList)
-      : mList(aList)
-      , mUnfinished(true)
-    {
-    }
-
-    ~RAII()
-    {
-      if (mUnfinished) {
-        ConsoleCallData* data = mList.popLast();
-        MOZ_ASSERT(data);
-        delete data;
-      }
-    }
-
-    void
-    Finished()
-    {
-      mUnfinished = false;
-    }
-
-  private:
-    LinkedList<ConsoleCallData>& mList;
-    bool mUnfinished;
-  };
-
-  ConsoleCallData* callData = new ConsoleCallData();
-  mQueuedCalls.insertBack(callData);
+  nsAutoPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
   callData->Initialize(aCx, aMethodName, aMethodString, aData);
-  RAII raii(mQueuedCalls);
 
   if (mWindow) {
     nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
     if (!webNav) {
       return;
     }
 
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
@@ -984,72 +1040,27 @@ Console::Method(JSContext* aCx, MethodNa
 
       TimeDuration duration =
         mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
 
       callData->mMonotonicTimer = duration.ToMilliseconds();
     }
   }
 
-  // The operation is completed. RAII class has to be disabled.
-  raii.Finished();
-
-  if (!NS_IsMainThread()) {
-    // Here we are in a worker thread. The ConsoleCallData has to been removed
-    // from the list and it will be deleted by the ConsoleCallDataRunnable or
-    // by the Main-Thread Console object.
-    mQueuedCalls.popLast();
-
-    nsRefPtr<ConsoleCallDataRunnable> runnable =
-      new ConsoleCallDataRunnable(callData);
-    runnable->Dispatch();
+  if (NS_IsMainThread()) {
+    callData->SetIDs(mOuterID, mInnerID);
+    ProcessCallData(callData);
     return;
   }
 
-  if (!mTimer) {
-    mTimer = do_CreateInstance("@mozilla.org/timer;1");
-    mTimer->InitWithCallback(this, CALL_DELAY,
-                             nsITimer::TYPE_REPEATING_SLACK);
-  }
-}
-
-void
-Console::AppendCallData(ConsoleCallData* aCallData)
-{
-  mQueuedCalls.insertBack(aCallData);
-
-  if (!mTimer) {
-    mTimer = do_CreateInstance("@mozilla.org/timer;1");
-    mTimer->InitWithCallback(this, CALL_DELAY,
-                             nsITimer::TYPE_REPEATING_SLACK);
-  }
-}
-
-// Timer callback used to process each of the queued calls.
-NS_IMETHODIMP
-Console::Notify(nsITimer *timer)
-{
-  MOZ_ASSERT(!mQueuedCalls.isEmpty());
-
-  for (uint32_t i = 0; i < MESSAGES_IN_INTERVAL; ++i) {
-    ConsoleCallData* data = mQueuedCalls.popFirst();
-    if (!data) {
-      break;
-    }
-
-    ProcessCallData(data);
-    delete data;
-  }
-
-  if (mQueuedCalls.isEmpty() && mTimer) {
-    mTimer->Cancel();
-    mTimer = nullptr;
-  }
-
-  return NS_OK;
+  // Note: we can pass the reference of callData because this runnable calls
+  // ProcessCallData() synchronously.
+  nsRefPtr<ConsoleCallDataRunnable> runnable =
+    new ConsoleCallDataRunnable(this, callData);
+  runnable->Dispatch();
 }
 
 // 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 {
@@ -1106,23 +1117,25 @@ Console::ProcessCallData(ConsoleCallData
   AutoSafeJSContext cx;
   ClearException ce(cx);
   RootedDictionary<ConsoleEvent> event(cx);
 
   JSAutoCompartment ac(cx, aData->mGlobal);
 
   event.mID.Construct();
   event.mInnerID.Construct();
-  if (mWindow) {
-    event.mID.Value().SetAsUnsignedLong() = mOuterID;
-    event.mInnerID.Value().SetAsUnsignedLong() = mInnerID;
+
+  MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
+  if (aData->mIDType == ConsoleCallData::eString) {
+    event.mID.Value().SetAsString() = aData->mOuterIDString;
+    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
   } else {
-    // If we are in a JSM, the window doesn't exist.
-    event.mID.Value().SetAsString() = NS_LITERAL_STRING("jsm");
-    event.mInnerID.Value().SetAsString() = frame.mFilename;
+    MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
+    event.mID.Value().SetAsUnsignedLong() = aData->mOuterIDNumber;
+    event.mInnerID.Value().SetAsUnsignedLong() = aData->mInnerIDNumber;
   }
 
   event.mLevel = aData->mMethodString;
   event.mFilename = frame.mFilename;
   event.mLineNumber = frame.mLineNumber;
   event.mColumnNumber = frame.mColumnNumber;
   event.mFunctionName = frame.mFunctionName;
   event.mTimeStamp = aData->mTimeStamp;
@@ -1238,38 +1251,30 @@ Console::ProcessCallData(ConsoleCallData
     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
   }
 
   if (!mStorage) {
     NS_WARNING("Failed to get the ConsoleAPIStorage service.");
     return;
   }
 
-  nsAutoString innerID;
-  innerID.AppendInt(mInnerID);
+  nsAutoString innerID, outerID;
 
-  if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
-    NS_WARNING("Failed to record a console event.");
+  MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
+  if (aData->mIDType == ConsoleCallData::eString) {
+    outerID = aData->mOuterIDString;
+    innerID = aData->mInnerIDString;
+  } else {
+    MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
+    outerID.AppendInt(aData->mOuterIDNumber);
+    innerID.AppendInt(aData->mInnerIDNumber);
   }
 
-  nsXPConnect*  xpc = nsXPConnect::XPConnect();
-  nsCOMPtr<nsISupports> wrapper;
-  const nsIID& iid = NS_GET_IID(nsISupports);
-
-  if (NS_FAILED(xpc->WrapJS(cx, eventObj, iid, getter_AddRefs(wrapper)))) {
-    return;
-  }
-
-  nsCOMPtr<nsIObserverService> obs =
-    do_GetService("@mozilla.org/observer-service;1");
-  if (obs) {
-    nsAutoString outerID;
-    outerID.AppendInt(mOuterID);
-
-    obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get());
+  if (NS_FAILED(mStorage->RecordPendingEvent(innerID, outerID, eventValue))) {
+    NS_WARNING("Failed to record a console event.");
   }
 }
 
 void
 Console::ProcessArguments(JSContext* aCx,
                           const nsTArray<JS::Heap<JS::Value>>& aData,
                           Sequence<JS::Value>& aSequence,
                           Sequence<JS::Value>& aStyles)
@@ -1684,32 +1689,43 @@ Console::IncreaseCounter(JSContext* aCx,
   JS::Rooted<JS::Value> value(aCx);
   if (!ToJSValue(aCx, data, &value)) {
     return JS::UndefinedValue();
   }
 
   return value;
 }
 
-void
-Console::ClearConsoleData()
-{
-  while (ConsoleCallData* data = mQueuedCalls.popFirst()) {
-    delete data;
-  }
-}
-
 bool
 Console::ShouldIncludeStackTrace(MethodName aMethodName)
 {
   switch (aMethodName) {
     case MethodError:
     case MethodException:
     case MethodAssert:
     case MethodTrace:
       return true;
     default:
       return false;
   }
 }
 
+nsIXPConnectJSObjectHolder*
+Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mSandbox) {
+    nsIXPConnect* xpc = nsContentUtils::XPConnect();
+    MOZ_ASSERT(xpc, "This should never be null!");
+
+    nsresult rv = xpc->CreateSandbox(aCx, aPrincipal,
+                                     getter_AddRefs(mSandbox));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+  }
+
+  return mSandbox;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -7,40 +7,37 @@
 #define mozilla_dom_Console_h
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
-#include "nsITimer.h"
 #include "nsWrapperCache.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsPIDOMWindow.h"
 
 class nsIConsoleAPIStorage;
+class nsIXPConnectJSObjectHolder;
 
 namespace mozilla {
 namespace dom {
 
 class ConsoleCallData;
 struct ConsoleStackEntry;
 
-class Console MOZ_FINAL : public nsITimerCallback
-                        , public nsIObserver
+class Console MOZ_FINAL : public nsIObserver
                         , public nsWrapperCache
 {
   ~Console();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console,
-                                                         nsITimerCallback)
-  NS_DECL_NSITIMERCALLBACK
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Console)
   NS_DECL_NSIOBSERVER
 
   explicit Console(nsPIDOMWindow* aWindow);
 
   // WebIDL methods
   nsISupports* GetParentObject() const
   {
     return mWindow;
@@ -127,19 +124,16 @@ private:
     MethodCount
   };
 
   void
   Method(JSContext* aCx, MethodName aName, const nsAString& aString,
          const Sequence<JS::Value>& aData);
 
   void
-  AppendCallData(ConsoleCallData* aData);
-
-  void
   ProcessCallData(ConsoleCallData* aData);
 
   // 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.
@@ -186,34 +180,34 @@ private:
   void
   ProfileMethod(JSContext* aCx, const nsAString& aAction,
                 const Sequence<JS::Value>& aData);
 
   JS::Value
   IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
                    const nsTArray<JS::Heap<JS::Value>>& aArguments);
 
-  void
-  ClearConsoleData();
-
   bool
   ShouldIncludeStackTrace(MethodName aMethodName);
 
+  nsIXPConnectJSObjectHolder*
+  GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
+
   nsCOMPtr<nsPIDOMWindow> mWindow;
-  nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
+  nsCOMPtr<nsIXPConnectJSObjectHolder> mSandbox;
 
-  LinkedList<ConsoleCallData> mQueuedCalls;
   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
 
   uint64_t mOuterID;
   uint64_t mInnerID;
 
   friend class ConsoleCallData;
+  friend class ConsoleRunnable;
   friend class ConsoleCallDataRunnable;
   friend class ConsoleProfileRunnable;
 };
 
 } // dom namespace
 } // mozilla namespace
 
 #endif /* mozilla_dom_Console_h */
--- a/dom/base/ConsoleAPIStorage.js
+++ b/dom/base/ConsoleAPIStorage.js
@@ -6,19 +6,28 @@
 
 let Cu = Components.utils;
 let Ci = Components.interfaces;
 let Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+// The console API events have to be scheduled when stored using
+// |recordPendingEvent|.
+const CALL_DELAY = 15 // milliseconds
+
+// This constant tells how many messages to process in a single timer execution.
+const MESSAGES_IN_INTERVAL = 1500
+
 const STORAGE_MAX_EVENTS = 200;
 
 var _consoleStorage = new Map();
+var _consolePendingStorage = new Map();
+var _timer;
 
 const CONSOLEAPISTORAGE_CID = Components.ID('{96cf7855-dfa9-4c6d-8276-f9705b4890f2}');
 
 /**
  * The ConsoleAPIStorage is meant to cache window.console API calls for later
  * reuse by other components when needed. For example, the Web Console code can
  * display the cached messages when it opens for the active tab.
  *
@@ -109,34 +118,80 @@ ConsoleAPIStorageService.prototype = {
    * Record an event associated with the given window ID.
    *
    * @param string aId
    *        The ID of the inner window for which the event occurred or "jsm" for
    *        messages logged from JavaScript modules..
    * @param object aEvent
    *        A JavaScript object you want to store.
    */
-  recordEvent: function CS_recordEvent(aId, aEvent)
+  recordEvent: function CS_recordEvent(aId, aOuterId, aEvent)
   {
     if (!_consoleStorage.has(aId)) {
       _consoleStorage.set(aId, []);
     }
 
     let storage = _consoleStorage.get(aId);
     storage.push(aEvent);
 
     // truncate
     if (storage.length > STORAGE_MAX_EVENTS) {
       storage.shift();
     }
 
+    Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId);
     Services.obs.notifyObservers(aEvent, "console-storage-cache-event", aId);
   },
 
   /**
+   * Similar to recordEvent, but these events are scheduled and stored any
+   * CALL_DELAY millisecs.
+   */
+  recordPendingEvent: function CS_recordPendingEvent(aId, aOuterId, aEvent)
+  {
+    if (!_consolePendingStorage.has(aId)) {
+      _consolePendingStorage.set(aId, []);
+    }
+
+    let storage = _consolePendingStorage.get(aId);
+    storage.push({ outerId: aOuterId, event: aEvent });
+
+    if (!_timer) {
+      _timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    }
+
+    let self = this;
+    _timer.initWithCallback(function() { self.flushPendingEvents(); },
+                            CALL_DELAY, Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+
+  /**
+   * Processes the pending event queue.
+   */
+  flushPendingEvents: function CS_flushPendingEvents()
+  {
+    for (let [id, objs] of _consolePendingStorage) {
+      for (let i = 0; i < objs.length && i < MESSAGES_IN_INTERVAL; ++i) {
+        this.recordEvent(id, objs[i].outerId, objs[i].event);
+      }
+
+      if (objs.length <= MESSAGES_IN_INTERVAL) {
+        _consolePendingStorage.delete(id);
+      } else {
+        _consolePendingStorage.set(id, objs.splice(MESSAGES_IN_INTERVAL));
+      }
+    }
+
+    if (_timer && _consolePendingStorage.size == 0) {
+      _timer.cancel();
+      _timer = null;
+    }
+  },
+
+  /**
    * Clear storage data for the given window.
    *
    * @param string [aId]
    *        Optional, the inner window ID for which you want to clear the
    *        messages. If this is not specified all of the cached messages are
    *        cleared, from all window objects.
    */
   clearEvents: function CS_clearEvents(aId)
--- a/dom/base/nsIConsoleAPIStorage.idl
+++ b/dom/base/nsIConsoleAPIStorage.idl
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(6701600a-17ca-417e-98f9-4ceb175dd15d)]
+[scriptable, uuid(cce39123-585e-411b-9edd-2513f7cf7e47)]
 interface nsIConsoleAPIStorage : nsISupports
 {
   /**
    * Get the events array by inner window ID or all events from all windows.
    *
    * @param string [aId]
    *        Optional, the inner window ID for which you want to get the array of
    *        cached events.
@@ -22,20 +22,39 @@ interface nsIConsoleAPIStorage : nsISupp
   jsval getEvents([optional] in DOMString aId);
 
   /**
    * Record an event associated with the given window ID.
    *
    * @param string aId
    *        The ID of the inner window for which the event occurred or "jsm" for
    *        messages logged from JavaScript modules..
+   * @param string aOuterId
+   *        This ID is used as 3rd parameters for the console-api-log-event
+   *        notification.
    * @param object aEvent
    *        A JavaScript object you want to store.
    */
-  void recordEvent(in DOMString aId, in jsval aEvent);
+  void recordEvent(in DOMString aId, in DOMString aOuterId, in jsval aEvent);
+
+  /**
+   * Similar to recordEvent() but these events will be collected
+   * and dispatched with a timer in order to avoid flooding the devtools
+   * webconsole.
+   *
+   * @param string aId
+   *        The ID of the inner window for which the event occurred or "jsm" for
+   *        messages logged from JavaScript modules..
+   * @param string aOuterId
+   *        This ID is used as 3rd parameters for the console-api-log-event
+   *        notification.
+   * @param object aEvent
+   *        A JavaScript object you want to store.
+   */
+  void recordPendingEvent(in DOMString aId, in DOMString aOuterId, in jsval aEvent);
 
   /**
    * Clear storage data for the given window.
    *
    * @param string [aId]
    *        Optional, the inner window ID for which you want to clear the
    *        messages. If this is not specified all of the cached messages are
    *        cleared, from all window objects.
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -46,16 +46,17 @@ support-files =
   onLine_worker_head.js
   promise_worker.js
   recursion_worker.js
   recursiveOnerror_worker.js
   relativeLoad_import.js
   relativeLoad_worker.js
   relativeLoad_worker2.js
   rvals_worker.js
+  sharedWorker_console.js
   sharedWorker_sharedWorker.js
   simpleThread_worker.js
   suspend_iframe.html
   suspend_worker.js
   terminate_worker.js
   terminateSyncXHR_frame.html
   terminateSyncXHR_worker.js
   testXHR.txt
@@ -110,16 +111,17 @@ support-files =
 [test_bug998474.html]
 [test_bug1063538.html]
 [test_chromeWorker.html]
 [test_clearTimeouts.html]
 [test_close.html]
 [test_closeOnGC.html]
 [test_console.html]
 [test_consoleReplaceable.html]
+[test_consoleSharedWorkers.html]
 [test_contentWorker.html]
 [test_csp.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_dataURLWorker.html]
 [test_errorPropagation.html]
 skip-if = buildapp == 'b2g' # b2g(times out) b2g-debug(times out) b2g-desktop(times out)
 [test_errorwarning.html]
 skip-if = buildapp == 'b2g' # b2g(Failed to load script: errorwarning_worker.js) b2g-debug(Failed to load script: errorwarning_worker.js) b2g-desktop(Failed to load script: errorwarning_worker.js)
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/sharedWorker_console.js
@@ -0,0 +1,11 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+onconnect = function(evt) {
+  console.profile("Hello profiling from a SharedWorker!");
+  console.log("Hello world from a SharedWorker!");
+  evt.ports[0].postMessage('ok!');
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_consoleSharedWorkers.html
@@ -0,0 +1,58 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test for console API in SharedWorker</title>
+    <script src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+  </head>
+  <body>
+    <script type="text/javascript">
+
+  function consoleListener() {
+    SpecialPowers.addObserver(this, "console-api-log-event", false);
+    SpecialPowers.addObserver(this, "console-api-profiler", false);
+  }
+
+  var order = 0;
+  consoleListener.prototype  = {
+    observe: function(aSubject, aTopic, aData) {
+      ok(true, "Something has been received");
+
+      if (aTopic == "console-api-profiler") {
+        var obj = aSubject.wrappedJSObject;
+        is (obj.arguments[0], "Hello profiling from a SharedWorker!", "A message from a SharedWorker \\o/");
+        is (order++, 0, "First a profiler message.");
+
+        SpecialPowers.removeObserver(this, "console-api-profiler");
+        return;
+      }
+
+      if (aTopic == "console-api-log-event") {
+        var obj = aSubject.wrappedJSObject;
+        is (obj.arguments[0], "Hello world from a SharedWorker!", "A message from a SharedWorker \\o/");
+        is (aData, "SharedWorker", "The ID is SharedWorker");
+        is (order++, 1, "Then a log message.");
+
+        SpecialPowers.removeObserver(this, "console-api-log-event");
+        SimpleTest.finish();
+        return;
+      }
+    }
+  }
+
+  var cl = new consoleListener();
+
+  SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() {
+    new SharedWorker('sharedWorker_console.js');
+  });
+
+  SimpleTest.waitForExplicitFinish();
+
+    </script>
+  </body>
+</html>
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -554,20 +554,21 @@ function sendConsoleAPIMessage(aConsole,
       catch (ex) {
         Cu.reportError(ex);
         Cu.reportError(ex.stack);
         return;
       }
       break;
   }
 
-  Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
   let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
                             .getService(Ci.nsIConsoleAPIStorage);
-  ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
+  if (ConsoleAPIStorage) {
+    ConsoleAPIStorage.recordEvent("jsm", null, consoleEvent);
+  }
 }
 
 /**
  * This creates a console object that somewhat replicates Firebug's console
  * object
  *
  * @param {object} aConsoleOptions
  *        Optional dictionary with a set of runtime console options: