Bug 965860 - patch 2 - ConsoleAPI written in C++, r=bz
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 27 Feb 2014 23:39:00 +0000
changeset 171458 3f528e61aacfeb743a6fab6fb47a9bfc10e282e2
parent 171457 ed97de4f1d285cf6fb1b197652b2e90890127de8
child 171459 428fa1a58cf365e5844c50bcb68c60f4fa2e0bd8
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbz
bugs965860
milestone30.0a1
Bug 965860 - patch 2 - ConsoleAPI written in C++, r=bz
addon-sdk/source/lib/sdk/console/plain-text.js
addon-sdk/source/lib/sdk/content/sandbox.js
addon-sdk/source/lib/sdk/deprecated/traits-worker.js
b2g/installer/package-manifest.in
browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
browser/installer/package-manifest.in
dom/base/Console.cpp
dom/base/Console.h
dom/base/ConsoleAPI.js
dom/base/ConsoleAPI.manifest
dom/base/moz.build
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/test/mochitest.ini
dom/base/test/test_consoleEmptyStack.html
dom/bindings/Bindings.conf
dom/interfaces/base/nsIDOMWindow.idl
dom/webidl/Console.webidl
dom/webidl/Window.webidl
dom/webidl/moz.build
mobile/android/chrome/content/browser.js
mobile/android/chrome/jar.mn
mobile/android/installer/package-manifest.in
toolkit/devtools/server/actors/webbrowser.js
--- a/addon-sdk/source/lib/sdk/console/plain-text.js
+++ b/addon-sdk/source/lib/sdk/console/plain-text.js
@@ -56,19 +56,16 @@ function PlainTextConsole(print) {
   // As we freeze the console object, we can't modify this property afterward
   Object.defineProperty(console, "maxLogLevel", {
     get: function() {
       return logLevel;
     }
   });
 
   // We defined the `__exposedProps__` in our console chrome object.
-  // Although it seems redundant, because we use `createObjectIn` too, in
-  // worker.js, we are following what `ConsoleAPI` does. See:
-  // http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#132
   //
   // Meanwhile we're investigating with the platform team if `__exposedProps__`
   // are needed, or are just a left-over.
 
   console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
     exposed[prop] = "r";
     return exposed;
   }, {});
--- a/addon-sdk/source/lib/sdk/content/sandbox.js
+++ b/addon-sdk/source/lib/sdk/content/sandbox.js
@@ -194,21 +194,17 @@ const WorkerSandbox = Class({
     }
 
     // Inject our `console` into target document if worker doesn't have a tab
     // (e.g Panel, PageWorker, Widget).
     // `worker.tab` can't be used because bug 804935.
     if (!getTabForContentWindow(window)) {
       let win = getUnsafeWindow(window);
 
-      // export our chrome console to content window, using the same approach
-      // of `ConsoleAPI`:
-      // http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
-      //
-      // and described here:
+      // export our chrome console to content window, as described here:
       // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
       let con = Cu.createObjectIn(win);
 
       let genPropDesc = function genPropDesc(fun) {
         return { enumerable: true, configurable: true, writable: true,
           value: console[fun] };
       }
 
--- a/addon-sdk/source/lib/sdk/deprecated/traits-worker.js
+++ b/addon-sdk/source/lib/sdk/deprecated/traits-worker.js
@@ -273,21 +273,17 @@ const WorkerSandbox = EventEmitter.compo
     }
 
     // Inject our `console` into target document if worker doesn't have a tab
     // (e.g Panel, PageWorker, Widget).
     // `worker.tab` can't be used because bug 804935.
     if (!getTabForContentWindow(window)) {
       let win = window.wrappedJSObject ? window.wrappedJSObject : window;
 
-      // export our chrome console to content window, using the same approach
-      // of `ConsoleAPI`:
-      // http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#150
-      //
-      // and described here:
+      // export our chrome console to content window as described here:
       // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
       let con = Cu.createObjectIn(win);
 
       let genPropDesc = function genPropDesc(fun) {
         return { enumerable: true, configurable: true, writable: true,
           value: console[fun] };
       }
 
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -334,17 +334,16 @@
 @BINPATH@/components/xulapp.xpt
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
-@BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/ConsoleAPIStorage.js
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
 @BINPATH@/components/NotificationStorage.js
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
@@ -28,19 +28,19 @@ function test() {
     let node = [...result.matched][0];
     ok(node, "found trace log node");
 
     let obj = node._messageObject;
     ok(obj, "console.trace message object");
 
     // The expected stack trace object.
     let stacktrace = [
-      { filename: TEST_URI, lineNumber: 9, functionName: "window.foobar585956c", language: 2 },
-      { filename: TEST_URI, lineNumber: 14, functionName: "foobar585956b", language: 2 },
-      { filename: TEST_URI, lineNumber: 18, functionName: "foobar585956a", language: 2 },
-      { filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 }
+      { filename: TEST_URI, functionName: "window.foobar585956c", language: 2, lineNumber: 9 },
+      { filename: TEST_URI, functionName: "foobar585956b", language: 2, lineNumber: 14 },
+      { filename: TEST_URI, functionName: "foobar585956a", language: 2, lineNumber: 18 },
+      { filename: TEST_URI, functionName: "", language: 2, lineNumber: 21 }
     ];
 
     ok(obj._stacktrace, "found stacktrace object");
     is(obj._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
     isnot(node.textContent.indexOf("bug-585956"), -1, "found file name");
   }
 }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -341,17 +341,16 @@
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 @BINPATH@/components/telemetry.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
-@BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/ConsoleAPIStorage.js
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/browser/components/BrowserFeeds.manifest
 @BINPATH@/browser/components/FeedConverter.js
 @BINPATH@/browser/components/FeedWriter.js
new file mode 100644
--- /dev/null
+++ b/dom/base/Console.cpp
@@ -0,0 +1,831 @@
+/* -*- 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 "mozilla/dom/Console.h"
+#include "mozilla/dom/ConsoleBinding.h"
+
+#include "mozilla/dom/Exceptions.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDocument.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsGlobalWindow.h"
+#include "nsJSUtils.h"
+#include "nsPerformance.h"
+
+#include "nsIConsoleAPIStorage.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoadContext.h"
+#include "nsIServiceManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWebNavigation.h"
+
+// The maximum allowed number of concurrent timers per page.
+#define MAX_PAGE_TIMERS 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
+
+using namespace mozilla::dom::exceptions;
+
+namespace mozilla {
+namespace dom {
+
+class ConsoleCallData
+{
+public:
+  ConsoleCallData()
+    : mMethodName(Console::MethodLog)
+    , mPrivate(false)
+    , mTimeStamp(JS_Now())
+    , mMonotonicTimer(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]);
+    }
+  }
+
+  JS::Heap<JSObject*> mGlobal;
+
+  Console::MethodName mMethodName;
+  bool mPrivate;
+  int64_t mTimeStamp;
+  DOMHighResTimeStamp mMonotonicTimer;
+
+  nsString mMethodString;
+  nsTArray<JS::Heap<JS::Value>> mArguments;
+  Sequence<ConsoleStackEntry> mStack;
+};
+
+// This class is used to clear any exception at the end of this method.
+class ClearException
+{
+public:
+  ClearException(JSContext* aCx)
+    : mCx(aCx)
+  {
+  }
+
+  ~ClearException()
+  {
+    JS_ClearPendingException(mCx);
+  }
+
+private:
+  JSContext* mCx;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
+
+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->mQueuedCalls.Clear();
+
+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 (uint32_t i = 0; i < tmp->mQueuedCalls.Length(); ++i) {
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mQueuedCalls[i].mGlobal);
+
+    for (uint32_t j = 0; j < tmp->mQueuedCalls[i].mArguments.Length(); ++j) {
+      NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mQueuedCalls[i].mArguments[j]);
+    }
+  }
+
+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(nsISupports)
+NS_INTERFACE_MAP_END
+
+Console::Console(nsPIDOMWindow* aWindow)
+  : mWindow(aWindow)
+  , mOuterID(0)
+  , mInnerID(0)
+{
+  if (mWindow) {
+    MOZ_ASSERT(mWindow->IsInnerWindow());
+    mInnerID = mWindow->WindowID();
+
+    nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
+    MOZ_ASSERT(outerWindow);
+    mOuterID = outerWindow->WindowID();
+  }
+
+  SetIsDOMBinding();
+  mozilla::HoldJSObjects(this);
+}
+
+Console::~Console()
+{
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return ConsoleBinding::Wrap(aCx, aScope, this);
+}
+
+#define METHOD(name, string)                                          \
+  void                                                                \
+  Console::name(JSContext* aCx, const Sequence<JS::Value>& aData)     \
+  {                                                                   \
+    Method(aCx, Method##name, NS_LITERAL_STRING(string), aData);      \
+  }
+
+METHOD(Log, "log")
+METHOD(Info, "info")
+METHOD(Warn, "warn")
+METHOD(Error, "error")
+METHOD(Exception, "exception")
+METHOD(Debug, "debug")
+
+void
+Console::Trace(JSContext* aCx)
+{
+  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(Group, "group")
+METHOD(GroupCollapsed, "groupCollapsed")
+METHOD(GroupEnd, "groupEnd")
+
+void
+Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
+{
+  Sequence<JS::Value> data;
+  SequenceRooter<JS::Value> rooter(aCx, &data);
+
+  if (!aTime.isUndefined()) {
+    data.AppendElement(aTime);
+  }
+
+  Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
+}
+
+void
+Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
+{
+  Sequence<JS::Value> data;
+  SequenceRooter<JS::Value> rooter(aCx, &data);
+
+  if (!aTime.isUndefined()) {
+    data.AppendElement(aTime);
+  }
+
+  Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
+}
+
+void
+Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
+                 ErrorResult& aRv)
+{
+  ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData, aRv);
+}
+
+void
+Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
+                    ErrorResult& aRv)
+{
+  ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData, aRv);
+}
+
+void
+Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
+                       const Sequence<JS::Value>& aData,
+                       ErrorResult& aRv)
+{
+  RootedDictionary<ConsoleProfileEvent> event(aCx);
+  event.mAction = aAction;
+
+  event.mArguments.Construct();
+  Sequence<JS::Value>& sequence = event.mArguments.Value();
+
+  for (uint32_t i = 0; i < aData.Length(); ++i) {
+    sequence.AppendElement(aData[i]);
+  }
+
+  JS::Rooted<JS::Value> eventValue(aCx);
+  if (!event.ToObject(aCx, JS::NullPtr(), &eventValue)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
+  MOZ_ASSERT(eventObj);
+
+  if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsXPConnect*  xpc = nsXPConnect::XPConnect();
+  nsCOMPtr<nsISupports> wrapper;
+  const nsIID& iid = NS_GET_IID(nsISupports);
+
+  if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> obs =
+    do_GetService("@mozilla.org/observer-service;1");
+  if (obs) {
+    obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
+  }
+}
+
+void
+Console::Assert(JSContext* aCx, bool aCondition,
+                const Sequence<JS::Value>& aData)
+{
+  if (!aCondition) {
+    Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
+  }
+}
+
+void
+Console::__noSuchMethod__()
+{
+  // Nothing to do.
+}
+
+// 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:
+    RAII(nsTArray<ConsoleCallData>& aArray)
+      : mArray(aArray)
+      , mUnfinished(true)
+    {
+    }
+
+    ~RAII()
+    {
+      if (mUnfinished) {
+        mArray.RemoveElementAt(mArray.Length() - 1);
+      }
+    }
+
+    void
+    Finished()
+    {
+      mUnfinished = false;
+    }
+
+  private:
+    nsTArray<ConsoleCallData>& mArray;
+    bool mUnfinished;
+  };
+
+  ConsoleCallData& callData = *mQueuedCalls.AppendElement();
+  callData.Initialize(aCx, aMethodName, aMethodString, aData);
+  RAII raii(mQueuedCalls);
+
+  if (mWindow) {
+    nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
+    if (!webNav) {
+      Throw(aCx, NS_ERROR_FAILURE);
+      return;
+    }
+
+    nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+    MOZ_ASSERT(loadContext);
+
+    loadContext->GetUsePrivateBrowsing(&callData.mPrivate);
+  }
+
+  uint32_t maxDepth = aMethodName == MethodTrace ?
+                       DEFAULT_MAX_STACKTRACE_DEPTH : 1;
+  nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
+
+  if (!stack) {
+    Throw(aCx, NS_ERROR_FAILURE);
+    return;
+  }
+
+  // nsIStackFrame is not thread-safe so we take what we need and we store in
+  // an array of ConsoleStackEntry objects.
+  do {
+    uint32_t language;
+    nsresult rv = stack->GetLanguage(&language);
+    if (NS_FAILED(rv)) {
+      Throw(aCx, rv);
+      return;
+    }
+
+    if (language == nsIProgrammingLanguage::JAVASCRIPT ||
+        language == nsIProgrammingLanguage::JAVASCRIPT2) {
+      ConsoleStackEntry& data = *callData.mStack.AppendElement();
+
+      nsCString string;
+      rv = stack->GetFilename(string);
+      if (NS_FAILED(rv)) {
+        Throw(aCx, rv);
+        return;
+      }
+
+      CopyUTF8toUTF16(string, data.mFilename);
+
+      int32_t lineNumber;
+      rv = stack->GetLineNumber(&lineNumber);
+      if (NS_FAILED(rv)) {
+        Throw(aCx, rv);
+        return;
+      }
+
+      data.mLineNumber = lineNumber;
+
+      rv = stack->GetName(string);
+      if (NS_FAILED(rv)) {
+        Throw(aCx, rv);
+        return;
+      }
+
+      CopyUTF8toUTF16(string, data.mFunctionName);
+
+      data.mLanguage = language;
+    }
+
+    nsCOMPtr<nsIStackFrame> caller;
+    rv = stack->GetCaller(getter_AddRefs(caller));
+    if (NS_FAILED(rv)) {
+      Throw(aCx, rv);
+      return;
+    }
+
+    stack.swap(caller);
+  } while (stack);
+
+  // Monotonic timer for 'time' and 'timeEnd'
+  if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) && mWindow) {
+    nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
+    MOZ_ASSERT(win);
+
+    ErrorResult rv;
+    nsRefPtr<nsPerformance> performance = win->GetPerformance(rv);
+    if (rv.Failed()) {
+      Throw(aCx, rv.ErrorCode());
+      return;
+    }
+
+    callData.mMonotonicTimer = performance->Now();
+  }
+
+  // The operation is completed. RAII class has to be disabled.
+  raii.Finished();
+
+  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());
+
+  uint32_t i = 0;
+  for (; i < MESSAGES_IN_INTERVAL && i < mQueuedCalls.Length();
+       ++i) {
+    ProcessCallData(mQueuedCalls[i]);
+  }
+
+  mQueuedCalls.RemoveElementsAt(0, i);
+
+  if (mQueuedCalls.IsEmpty()) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  return NS_OK;
+}
+
+void
+Console::ProcessCallData(ConsoleCallData& aData)
+{
+  ConsoleStackEntry frame;
+  if (!aData.mStack.IsEmpty()) {
+    frame = aData.mStack[0];
+  }
+
+  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;
+  } 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;
+  }
+
+  event.mLevel = aData.mMethodString;
+  event.mFilename = frame.mFilename;
+  event.mLineNumber = frame.mLineNumber;
+  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();
+      ProcessArguments(cx, aData.mArguments, event.mArguments.Value());
+      break;
+
+    default:
+      event.mArguments.Construct();
+      ArgumentsToValueList(aData.mArguments, event.mArguments.Value());
+  }
+
+  if (aData.mMethodName == MethodTrace) {
+    event.mStacktrace.Construct();
+    event.mStacktrace.Value().SwapElements(aData.mStack);
+  }
+
+  else if (aData.mMethodName == MethodGroup ||
+           aData.mMethodName == MethodGroupCollapsed ||
+           aData.mMethodName == MethodGroupEnd) {
+    ComposeGroupName(cx, aData.mArguments, event.mGroupName);
+  }
+
+  else if (aData.mMethodName == MethodTime && !aData.mArguments.IsEmpty()) {
+    event.mTimer = StartTimer(cx, aData.mArguments[0], aData.mMonotonicTimer);
+  }
+
+  else if (aData.mMethodName == MethodTimeEnd && !aData.mArguments.IsEmpty()) {
+    event.mTimer = StopTimer(cx, aData.mArguments[0], aData.mMonotonicTimer);
+  }
+
+  JS::Rooted<JS::Value> eventValue(cx);
+  if (!event.ToObject(cx, JS::NullPtr(), &eventValue)) {
+    Throw(cx, NS_ERROR_FAILURE);
+    return;
+  }
+
+  JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
+  MOZ_ASSERT(eventObj);
+
+  if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue,
+                         nullptr, nullptr, JSPROP_ENUMERATE)) {
+    return;
+  }
+
+  if (!mStorage) {
+    mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
+  }
+
+  if (!mStorage) {
+    NS_WARNING("Failed to get the ConsoleAPIStorage service.");
+    return;
+  }
+
+  nsAutoString innerID;
+  innerID.AppendInt(mInnerID);
+
+  if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
+    NS_WARNING("Failed to record a console event.");
+  }
+
+  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());
+  }
+}
+
+void
+Console::ProcessArguments(JSContext* aCx,
+                          const nsTArray<JS::Heap<JS::Value>>& aData,
+                          Sequence<JS::Value>& aSequence)
+{
+  if (aData.IsEmpty()) {
+    return;
+  }
+
+  if (aData.Length() == 1 || !aData[0].isString()) {
+    ArgumentsToValueList(aData, aSequence);
+    return;
+  }
+
+  SequenceRooter<JS::Value> rooter(aCx, &aSequence);
+
+  JS::Rooted<JS::Value> format(aCx, aData[0]);
+  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
+  if (!jsString) {
+    return;
+  }
+
+  nsDependentJSString string;
+  if (!string.init(aCx, jsString)) {
+    return;
+  }
+
+  nsString::const_iterator start, end;
+  string.BeginReading(start);
+  string.EndReading(end);
+
+  nsString output;
+  uint32_t index = 1;
+
+  while (start != end) {
+    if (*start != '%') {
+      output.Append(*start);
+      ++start;
+      continue;
+    }
+
+    ++start;
+
+    if (*start == '%') {
+      output.Append(*start);
+      ++start;
+      continue;
+    }
+
+    char ch = *start;
+    ++start;
+
+    switch (ch) {
+      case 'o':
+      {
+        if (!output.IsEmpty()) {
+          JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
+                                                             output.get(),
+                                                             output.Length()));
+          if (!str) {
+            return;
+          }
+
+          aSequence.AppendElement(JS::StringValue(str));
+          output.Truncate();
+        }
+
+        JS::Rooted<JS::Value> v(aCx);
+        if (index < aData.Length()) {
+          v = aData[index++];
+        }
+
+        aSequence.AppendElement(v);
+        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) {
+            return;
+          }
+
+          nsDependentJSString v;
+          if (!v.init(aCx, jsString)) {
+            return;
+          }
+
+          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)) {
+            return;
+          }
+
+          output.AppendPrintf("%d", v);
+        }
+        break;
+
+      case 'f':
+        if (index < aData.Length()) {
+          JS::Rooted<JS::Value> value(aCx, aData[index++]);
+
+          double v;
+          if (!JS::ToNumber(aCx, value, &v)) {
+            return;
+          }
+
+          output.AppendPrintf("%f", v);
+        }
+        break;
+
+      default:
+        output.Append(ch);
+        break;
+    }
+  }
+
+  if (!output.IsEmpty()) {
+    JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
+                                                       output.Length()));
+    if (!str) {
+      return;
+    }
+
+    aSequence.AppendElement(JS::StringValue(str));
+  }
+
+  // The rest of the array, if unused by the format string.
+  for (; index < aData.Length(); ++index) {
+    aSequence.AppendElement(aData[index]);
+  }
+}
+
+void
+Console::ComposeGroupName(JSContext* aCx,
+                          const nsTArray<JS::Heap<JS::Value>>& aData,
+                          nsAString& aName)
+{
+  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) {
+      return;
+    }
+
+    nsDependentJSString string;
+    if (!string.init(aCx, jsString)) {
+      return;
+    }
+
+    aName.Append(string);
+  }
+}
+
+JS::Value
+Console::StartTimer(JSContext* aCx, const JS::Value& aName,
+                    DOMHighResTimeStamp aTimestamp)
+{
+  if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
+    RootedDictionary<ConsoleTimerError> error(aCx);
+
+    JS::Rooted<JS::Value> value(aCx);
+    if (!error.ToObject(aCx, JS::NullPtr(), &value)) {
+      return JS::UndefinedValue();
+    }
+
+    return value;
+  }
+
+  RootedDictionary<ConsoleTimerStart> timer(aCx);
+
+  JS::Rooted<JS::Value> name(aCx, aName);
+  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
+  if (!jsString) {
+    return JS::UndefinedValue();
+  }
+
+  nsDependentJSString key;
+  if (!key.init(aCx, jsString)) {
+    return JS::UndefinedValue();
+  }
+
+  timer.mName = key;
+
+  DOMHighResTimeStamp entry;
+  if (!mTimerRegistry.Get(key, &entry)) {
+    mTimerRegistry.Put(key, aTimestamp);
+  } else {
+    aTimestamp = entry;
+  }
+
+  timer.mStarted = aTimestamp;
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!timer.ToObject(aCx, JS::NullPtr(), &value)) {
+    return JS::UndefinedValue();
+  }
+
+  return value;
+}
+
+JS::Value
+Console::StopTimer(JSContext* aCx, const JS::Value& aName,
+                   DOMHighResTimeStamp aTimestamp)
+{
+  JS::Rooted<JS::Value> name(aCx, aName);
+  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
+  if (!jsString) {
+    return JS::UndefinedValue();
+  }
+
+  nsDependentJSString key;
+  if (!key.init(aCx, jsString)) {
+    return JS::UndefinedValue();
+  }
+
+  DOMHighResTimeStamp entry;
+  if (!mTimerRegistry.Get(key, &entry)) {
+    return JS::UndefinedValue();
+  }
+
+  mTimerRegistry.Remove(key);
+
+  RootedDictionary<ConsoleTimerEnd> timer(aCx);
+  timer.mName = key;
+  timer.mDuration = aTimestamp - entry;
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!timer.ToObject(aCx, JS::NullPtr(), &value)) {
+    return JS::UndefinedValue();
+  }
+
+  return value;
+}
+
+void
+Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
+                              Sequence<JS::Value>& aSequence)
+{
+  for (uint32_t i = 0; i < aData.Length(); ++i) {
+    aSequence.AppendElement(aData[i]);
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/Console.h
@@ -0,0 +1,180 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_Console_h
+#define mozilla_dom_Console_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/UnionConversions.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsITimer.h"
+#include "nsWrapperCache.h"
+
+class nsIConsoleAPIStorage;
+
+namespace mozilla {
+namespace dom {
+
+class ConsoleCallData;
+
+class Console MOZ_FINAL : public nsITimerCallback
+                        , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Console)
+  NS_DECL_NSITIMERCALLBACK
+
+  Console(nsPIDOMWindow* aWindow);
+  ~Console();
+
+  // WebIDL methods
+  nsISupports* GetParentObject() const
+  {
+    return mWindow;
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  void
+  Log(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Info(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Warn(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Error(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Exception(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Debug(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Trace(JSContext* aCx);
+
+  void
+  Dir(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Group(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  GroupCollapsed(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  GroupEnd(JSContext* aCx, const Sequence<JS::Value>& aData);
+
+  void
+  Time(JSContext* aCx, const JS::Handle<JS::Value> aTime);
+
+  void
+  TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime);
+
+  void
+  Profile(JSContext* aCx, const Sequence<JS::Value>& aData,
+          ErrorResult& aRv);
+
+  void
+  ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData,
+             ErrorResult& aRv);
+
+  void
+  Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
+
+  void
+  __noSuchMethod__();
+
+private:
+  enum MethodName
+  {
+    MethodLog,
+    MethodInfo,
+    MethodWarn,
+    MethodError,
+    MethodException,
+    MethodDebug,
+    MethodTrace,
+    MethodDir,
+    MethodGroup,
+    MethodGroupCollapsed,
+    MethodGroupEnd,
+    MethodTime,
+    MethodTimeEnd,
+    MethodAssert
+  };
+
+  void
+  Method(JSContext* aCx, MethodName aName, const nsAString& aString,
+         const Sequence<JS::Value>& 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    - a JS object.
+  // The output is an array where any object is a separated item, the rest is
+  // unified in a format string.
+  // Example if the input is:
+  //   "string: %s, integer: %d, object: %o, double: %d", 's', 1, window, 0.9
+  // The output will be:
+  //   [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
+  void
+  ProcessArguments(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData,
+                   Sequence<JS::Value>& aSequence);
+
+  // Stringify and Concat all the JS::Value in a single string using ' ' as
+  // separator.
+  void
+  ComposeGroupName(JSContext* aCx, const nsTArray<JS::Heap<JS::Value>>& aData,
+                   nsAString& aName);
+
+  JS::Value
+  StartTimer(JSContext* aCx, const JS::Value& aName,
+             DOMHighResTimeStamp aTimestamp);
+
+  JS::Value
+  StopTimer(JSContext* aCx, const JS::Value& aName,
+            DOMHighResTimeStamp aTimestamp);
+
+  // The method populates a Sequence from an array of JS::Value.
+  void
+  ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
+                       Sequence<JS::Value>& aSequence);
+
+  void
+  ProfileMethod(JSContext* aCx, const nsAString& aAction,
+                const Sequence<JS::Value>& aData,
+                ErrorResult& aRv);
+
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIConsoleAPIStorage> mStorage;
+
+  nsTArray<ConsoleCallData> mQueuedCalls;
+  nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
+
+  uint64_t mOuterID;
+  uint64_t mInnerID;
+
+  friend class ConsoleCallData;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif /* mozilla_dom_Console_h */
deleted file mode 100644
--- a/dom/base/ConsoleAPI.js
+++ /dev/null
@@ -1,563 +0,0 @@
-/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* 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/. */
-
-let Cu = Components.utils;
-let Ci = Components.interfaces;
-let Cc = Components.classes;
-
-// The maximum allowed number of concurrent timers per page.
-const MAX_PAGE_TIMERS = 10000;
-
-// The maximum allowed number of concurrent counters per page.
-const MAX_PAGE_COUNTERS = 10000;
-
-// The regular expression used to parse %s/%d and other placeholders for
-// variables in strings that need to be interpolated.
-const ARGUMENT_PATTERN = /%\d*\.?\d*([osdif])\b/g;
-
-// The maximum stacktrace depth when populating the stacktrace array used for
-// console.trace().
-const DEFAULT_MAX_STACKTRACE_DEPTH = 200;
-
-// The console API methods are async and their action is executed later. This
-// delay tells how much later.
-const CALL_DELAY = 15; // milliseconds
-
-// This constant tells how many messages to process in a single timer execution.
-const MESSAGES_IN_INTERVAL = 1500;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "ConsoleAPIStorage",
-                                   "@mozilla.org/consoleAPI-storage;1",
-                                   "nsIConsoleAPIStorage");
-
-/**
- * The window.console API implementation. One instance is lazily created for
- * every inner window, when the window.console object is accessed.
- */
-function ConsoleAPI() {}
-ConsoleAPI.prototype = {
-
-  classID: Components.ID("{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}"),
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsIObserver]),
-
-  _timerInitialized: false,
-  _queuedCalls: null,
-  _window: null,
-  _innerID: null,
-  _outerID: null,
-  _windowDestroyed: false,
-  _timer: null,
-
-  // nsIDOMGlobalPropertyInitializer
-  init: function CA_init(aWindow) {
-    Services.obs.addObserver(this, "inner-window-destroyed", true);
-
-    try {
-      let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDOMWindowUtils);
-
-      this._outerID = windowUtils.outerWindowID;
-      this._innerID = windowUtils.currentInnerWindowID;
-    }
-    catch (ex) {
-      Cu.reportError(ex);
-    }
-
-    let self = this;
-    let chromeObject = {
-      // window.console API
-      log: function CA_log() {
-        self.queueCall("log", arguments);
-      },
-      info: function CA_info() {
-        self.queueCall("info", arguments);
-      },
-      warn: function CA_warn() {
-        self.queueCall("warn", arguments);
-      },
-      error: function CA_error() {
-        self.queueCall("error", arguments);
-      },
-      exception: function CA_exception() {
-        self.queueCall("exception", arguments);
-      },
-      debug: function CA_debug() {
-        self.queueCall("debug", arguments);
-      },
-      trace: function CA_trace() {
-        self.queueCall("trace", arguments);
-      },
-      // Displays an interactive listing of all the properties of an object.
-      dir: function CA_dir() {
-        self.queueCall("dir", arguments);
-      },
-      group: function CA_group() {
-        self.queueCall("group", arguments);
-      },
-      groupCollapsed: function CA_groupCollapsed() {
-        self.queueCall("groupCollapsed", arguments);
-      },
-      groupEnd: function CA_groupEnd() {
-        self.queueCall("groupEnd", arguments);
-      },
-      time: function CA_time() {
-        self.queueCall("time", arguments);
-      },
-      timeEnd: function CA_timeEnd() {
-        self.queueCall("timeEnd", arguments);
-      },
-      profile: function CA_profile() {
-        // Send a notification picked up by the profiler if installed.
-        // This must happen right away otherwise we will miss samples
-        let consoleEvent = {
-          action: "profile",
-          arguments: arguments
-        };
-        consoleEvent.wrappedJSObject = consoleEvent;
-        Services.obs.notifyObservers(consoleEvent, "console-api-profiler",
-                                     null);  
-      },
-      profileEnd: function CA_profileEnd() {
-        // Send a notification picked up by the profiler if installed.
-        // This must happen right away otherwise we will miss samples
-        let consoleEvent = {
-          action: "profileEnd",
-          arguments: arguments
-        };
-        consoleEvent.wrappedJSObject = consoleEvent;
-        Services.obs.notifyObservers(consoleEvent, "console-api-profiler",
-                                     null);  
-      },
-      assert: function CA_assert() {
-        let args = Array.prototype.slice.call(arguments);
-        if(!args.shift()) {
-          self.queueCall("assert", args);
-        }
-      },
-      count: function CA_count() {
-        self.queueCall("count", arguments);
-      },
-      __exposedProps__: {
-        log: "r",
-        info: "r",
-        warn: "r",
-        error: "r",
-        exception: "r",
-        debug: "r",
-        trace: "r",
-        dir: "r",
-        group: "r",
-        groupCollapsed: "r",
-        groupEnd: "r",
-        time: "r",
-        timeEnd: "r",
-        profile: "r",
-        profileEnd: "r",
-        assert: "r",
-        count: "r"
-      }
-    };
-
-    // We need to return an actual content object here, instead of a wrapped
-    // chrome object. This allows things like console.log.bind() to work.
-    let contentObj = Cu.createObjectIn(aWindow);
-    function genPropDesc(fun) {
-      return { enumerable: true, configurable: true, writable: true,
-               value: chromeObject[fun].bind(chromeObject) };
-    }
-    const properties = {
-      log: genPropDesc('log'),
-      info: genPropDesc('info'),
-      warn: genPropDesc('warn'),
-      error: genPropDesc('error'),
-      exception: genPropDesc('exception'),
-      debug: genPropDesc('debug'),
-      trace: genPropDesc('trace'),
-      dir: genPropDesc('dir'),
-      group: genPropDesc('group'),
-      groupCollapsed: genPropDesc('groupCollapsed'),
-      groupEnd: genPropDesc('groupEnd'),
-      time: genPropDesc('time'),
-      timeEnd: genPropDesc('timeEnd'),
-      profile: genPropDesc('profile'),
-      profileEnd: genPropDesc('profileEnd'),
-      assert: genPropDesc('assert'),
-      count: genPropDesc('count'),
-      __noSuchMethod__: { enumerable: true, configurable: true, writable: true,
-                          value: function() {} },
-      __mozillaConsole__: { value: true }
-    };
-
-    Object.defineProperties(contentObj, properties);
-    Cu.makeObjectPropsNormal(contentObj);
-
-    this._queuedCalls = [];
-    this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this._window = Cu.getWeakReference(aWindow);
-    this.timerRegistry = new Map();
-    this.counterRegistry = new Map();
-
-    return contentObj;
-  },
-
-  observe: function CA_observe(aSubject, aTopic, aData)
-  {
-    if (aTopic == "inner-window-destroyed") {
-      let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
-      if (innerWindowID == this._innerID) {
-        Services.obs.removeObserver(this, "inner-window-destroyed");
-        this._windowDestroyed = true;
-        if (!this._timerInitialized) {
-          this.timerRegistry.clear();
-        }
-      }
-    }
-  },
-
-  /**
-   * Queue a call to a console method. See the CALL_DELAY constant.
-   * This method is the entry point for the console.* for workers.
-   *
-   * @param string aMethod
-   *        The console method the code has invoked.
-   * @param object aArguments
-   *        The arguments passed to the console method.
-   * @param array aStack
-   *        The stack of the console method. Used by console.* for workers.
-   */
-  queueCall: function CA_queueCall(aMethod, aArguments, aStack = null)
-  {
-    let window = this._window.get();
-    let metaForCall = {
-      private: PrivateBrowsingUtils.isWindowPrivate(window),
-      timeStamp: Date.now(),
-      stack: (aStack ? aStack : this.getStackTrace(aMethod != "trace" ? 1 : null)),
-    };
-
-    if (aMethod == "time" || aMethod == "timeEnd") {
-      metaForCall.monotonicTimer = window.performance.now();
-    }
-
-    this._queuedCalls.push([aMethod, aArguments, metaForCall]);
-
-    if (!this._timerInitialized) {
-      this._timer.initWithCallback(this._timerCallback.bind(this), CALL_DELAY,
-                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
-      this._timerInitialized = true;
-    }
-  },
-
-  /**
-   * Timer callback used to process each of the queued calls.
-   * @private
-   */
-  _timerCallback: function CA__timerCallback()
-  {
-    this._queuedCalls.splice(0, MESSAGES_IN_INTERVAL)
-      .forEach(this._processQueuedCall, this);
-
-    if (!this._queuedCalls.length) {
-      this._timerInitialized = false;
-      this._timer.cancel();
-
-      if (this._windowDestroyed) {
-        ConsoleAPIStorage.clearEvents(this._innerID);
-        this.timerRegistry.clear();
-      }
-    }
-  },
-
-  /**
-   * Process a queued call to a console method.
-   *
-   * @private
-   * @param array aCall
-   *        Array that holds information about the queued call.
-   */
-  _processQueuedCall: function CA__processQueuedItem(aCall)
-  {
-    let [method, args, meta] = aCall;
-
-    let frame;
-    if (meta.stack.length) {
-      frame = meta.stack[0];
-    } else {
-      frame = {
-        filename: "",
-        lineNumber: 0,
-        functionName: "",
-      };
-    }
-
-    let consoleEvent = {
-      ID: this._outerID,
-      innerID: this._innerID,
-      level: method,
-      filename: frame.filename,
-      lineNumber: frame.lineNumber,
-      functionName: frame.functionName,
-      timeStamp: meta.timeStamp,
-      arguments: args,
-      private: meta.private,
-    };
-
-    switch (method) {
-      case "log":
-      case "info":
-      case "warn":
-      case "error":
-      case "exception":
-      case "debug":
-      case "assert":
-        consoleEvent.arguments = this.processArguments(args);
-        break;
-      case "trace":
-        consoleEvent.stacktrace = meta.stack;
-        break;
-      case "group":
-      case "groupCollapsed":
-      case "groupEnd":
-        try {
-          consoleEvent.groupName = Array.prototype.join.call(args, " ");
-        }
-        catch (ex) {
-          Cu.reportError(ex);
-          Cu.reportError(ex.stack);
-          return;
-        }
-        break;
-      case "dir":
-        break;
-      case "time":
-        consoleEvent.timer = this.startTimer(args[0], meta.monotonicTimer);
-        break;
-      case "timeEnd":
-        consoleEvent.timer = this.stopTimer(args[0], meta.monotonicTimer);
-        break;
-      case "count":
-        consoleEvent.counter = this.increaseCounter(frame, args[0]);
-        break;
-      default:
-        // unknown console API method!
-        return;
-    }
-
-    this.notifyObservers(method, consoleEvent);
-  },
-
-  /**
-   * Notify all observers of any console API call.
-   *
-   * @param string aLevel
-   *        The message level.
-   * @param object aConsoleEvent
-   *        The console event object to send to observers for the given console
-   *        API call.
-   */
-  notifyObservers: function CA_notifyObservers(aLevel, aConsoleEvent)
-  {
-    aConsoleEvent.wrappedJSObject = aConsoleEvent;
-    ConsoleAPIStorage.recordEvent(this._innerID, aConsoleEvent);
-    Services.obs.notifyObservers(aConsoleEvent, "console-api-log-event",
-                                 this._outerID);
-  },
-
-  /**
-   * Process the console API call arguments in order to perform printf-like
-   * string substitution.
-   *
-   * TODO: object substitution should take into account width and precision
-   * qualifiers (bug 685813).
-   *
-   * @param mixed aArguments
-   *        The arguments given to the console API call.
-   **/
-  processArguments: function CA_processArguments(aArguments) {
-    if (aArguments.length < 2 || typeof aArguments[0] != "string") {
-      return aArguments;
-    }
-
-    let args = Array.prototype.slice.call(aArguments);
-    let format = args.shift();
-    let splitter = "%" + format.length + Date.now() + "%";
-    let objects = [];
-
-    // Format specification regular expression.
-    let processed = format.replace(ARGUMENT_PATTERN, function CA_PA_substitute(match, submatch) {
-      switch (submatch) {
-        case "o":
-          objects.push(args.shift());
-          return splitter;
-        case "s":
-          return String(args.shift());
-        case "d":
-        case "i":
-          return parseInt(args.shift());
-        case "f":
-          return parseFloat(args.shift());
-        default:
-          return submatch;
-      };
-    });
-
-    let result = [];
-    let processedArray = processed.split(splitter);
-    processedArray.forEach(function(aValue, aIndex) {
-      if (aValue !== "") {
-        result.push(aValue);
-      }
-      if (objects[aIndex]) {
-        result.push(objects[aIndex]);
-      }
-    });
-
-    return result.concat(args);
-  },
-
-  /**
-   * Build the stacktrace array for the console.trace() call.
-   *
-   * @param number [aMaxDepth=DEFAULT_MAX_STACKTRACE_DEPTH]
-   *        Optional maximum stacktrace depth.
-   * @return array
-   *         Each element is a stack frame that holds the following properties:
-   *         filename, lineNumber, functionName and language.
-   */
-  getStackTrace: function CA_getStackTrace(aMaxDepth) {
-    if (!aMaxDepth) {
-      aMaxDepth = DEFAULT_MAX_STACKTRACE_DEPTH;
-    }
-
-    let stack = [];
-    let frame = Components.stack.caller.caller;
-    while (frame = frame.caller) {
-      if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT ||
-          frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) {
-        stack.push({
-          filename: frame.filename,
-          lineNumber: frame.lineNumber,
-          functionName: frame.name,
-          language: frame.language,
-        });
-        if (stack.length == aMaxDepth) {
-          break;
-        }
-      }
-    }
-
-    return stack;
-  },
-
-  /*
-   * A registry of started timers.
-   * @type Map
-   */
-  timerRegistry: null,
-
-  /**
-   * Create a new timer by recording the current time under the specified name.
-   *
-   * @param string aName
-   *        The name of the timer.
-   * @param number aTimestamp
-   *        A monotonic strictly-increasing timing value that tells when the
-   *        timer was started.
-   * @return object
-   *        The name property holds the timer name and the started property
-   *        holds the time the timer was started. In case of error, it returns
-   *        an object with the single property "error" that contains the key
-   *        for retrieving the localized error message.
-   **/
-  startTimer: function CA_startTimer(aName, aTimestamp) {
-    if (!aName) {
-      return;
-    }
-    if (this.timerRegistry.size > MAX_PAGE_TIMERS - 1) {
-      return { error: "maxTimersExceeded" };
-    }
-    let key = aName.toString();
-    if (!this.timerRegistry.has(key)) {
-      this.timerRegistry.set(key, aTimestamp);
-    }
-    return { name: aName, started: this.timerRegistry.get(key) };
-  },
-
-  /**
-   * Stop the timer with the specified name and retrieve the elapsed time.
-   *
-   * @param string aName
-   *        The name of the timer.
-   * @param number aTimestamp
-   *        A monotonic strictly-increasing timing value that tells when the
-   *        timer was stopped.
-   * @return object
-   *        The name property holds the timer name and the duration property
-   *        holds the number of milliseconds since the timer was started.
-   **/
-  stopTimer: function CA_stopTimer(aName, aTimestamp) {
-    if (!aName) {
-      return;
-    }
-    let key = aName.toString();
-    if (!this.timerRegistry.has(key)) {
-      return;
-    }
-    let duration = aTimestamp - this.timerRegistry.get(key);
-    this.timerRegistry.delete(key);
-    return { name: aName, duration: duration };
-  },
-
-  /*
-   * A registry of counsole.count() counters.
-   * @type Map
-   */
-  counterRegistry: null,
-
-  /**
-   * Increases the given counter by one or creates a new counter if the label
-   * is not known so far.
-   *
-   * @param object aFrame
-   *        The current stack frame to extract the filename and linenumber
-   *        from the console.count() invocation.
-   * @param string aLabel
-   *        The label of the counter. If no label is provided, the script url
-   *        and line number is used for associating the counters
-   * @return object
-   *        The label property holds the counters label and the count property
-   *        holds the current count.
-   **/
-  increaseCounter: function CA_increaseCounter(aFrame, aLabel) {
-    let key = null, label = null;
-    try {
-      label = key = aLabel ? aLabel + "" : "";
-    } catch (ex) { }
-    if (!key) {
-      key = aFrame.filename + ":" + aFrame.lineNumber;
-    }
-    let counter = null;
-    if (!this.counterRegistry.has(key)) {
-      if (this.counterRegistry.size > MAX_PAGE_COUNTERS - 1) {
-        return { error: "maxCountersExceeded" };
-      }
-      counter = { label: label, count: 1 };
-      this.counterRegistry.set(key, counter);
-    } else {
-      counter = this.counterRegistry.get(key);
-      counter.count += 1;
-    }
-    return { label: counter.label, count: counter.count };
-  }
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPI]);
--- a/dom/base/ConsoleAPI.manifest
+++ b/dom/base/ConsoleAPI.manifest
@@ -1,5 +1,2 @@
-component {b49c18f8-3379-4fc0-8c90-d7772c1a9ff3} ConsoleAPI.js
-contract @mozilla.org/console-api;1 {b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}
-category JavaScript-global-property console @mozilla.org/console-api;1
 component {96cf7855-dfa9-4c6d-8276-f9705b4890f2} ConsoleAPIStorage.js
 contract @mozilla.org/consoleAPI-storage;1 {96cf7855-dfa9-4c6d-8276-f9705b4890f2}
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -45,16 +45,17 @@ EXPORTS += [
     'nsStructuredCloneContainer.h',
     'nsWindowMemoryReporter.h',
     'nsWrapperCache.h',
     'nsWrapperCacheInlines.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'BarProps.h',
+    'Console.h',
     'DOMCursor.h',
     'DOMError.h',
     'DOMException.h',
     'DOMRequest.h',
     'MessageChannel.h',
     'MessagePort.h',
     'MessagePortList.h',
     'Navigator.h',
@@ -63,16 +64,17 @@ EXPORTS.mozilla.dom += [
     'StructuredCloneTags.h',
     'URL.h',
     'URLSearchParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'BarProps.cpp',
     'CompositionStringSynthesizer.cpp',
+    'Console.cpp',
     'Crypto.cpp',
     'DOMCursor.cpp',
     'DOMError.cpp',
     'DOMException.cpp',
     'DOMRequest.cpp',
     'MessageChannel.cpp',
     'MessagePortList.cpp',
     'Navigator.cpp',
@@ -113,17 +115,16 @@ SOURCES += [
     'nsGlobalWindow.cpp',
     # This file forces NSPR logging.
     'nsJSEnvironment.cpp',
     # nsPluginArray.cpp includes npapi.h indirectly, and that includes a lot of system headers
     'nsPluginArray.cpp',
 ]
 
 EXTRA_COMPONENTS += [
-    'ConsoleAPI.js',
     'ConsoleAPI.manifest',
     'ConsoleAPIStorage.js',
     'SiteSpecificUserAgent.js',
     'SiteSpecificUserAgent.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'DOMRequestHelper.jsm',
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -202,16 +202,17 @@
 #include "nsHTMLDocument.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsDOMEventTargetHelper.h"
 #include "prrng.h"
 #include "nsSandboxFlags.h"
 #include "TimeChangeObserver.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/BrowserElementDictionariesBinding.h"
+#include "mozilla/dom/Console.h"
 #include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
@@ -1434,16 +1435,18 @@ nsGlobalWindow::CleanUp()
   mScrollbars = nullptr;
   mLocation = nullptr;
   mHistory = nullptr;
   mFrames = nullptr;
   mWindowUtils = nullptr;
   mApplicationCache = nullptr;
   mIndexedDB = nullptr;
 
+  mConsole = nullptr;
+
   mPerformance = nullptr;
 
 #ifdef MOZ_WEBSPEECH
   mSpeechSynthesis = nullptr;
 #endif
 
   ClearControllers();
 
@@ -1748,16 +1751,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
   nsGlobalWindow::CleanupCachedXBLHandlers(tmp);
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
@@ -1805,16 +1809,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 #ifdef DEBUG
 void
 nsGlobalWindow::RiskyUnlink()
 {
   NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
 }
@@ -13355,16 +13360,37 @@ nsGlobalWindow::SetHasAudioAvailableEven
 {
   MOZ_ASSERT(IsInnerWindow());
 
   if (mDoc) {
     mDoc->NotifyAudioAvailableListener();
   }
 }
 
+NS_IMETHODIMP
+nsGlobalWindow::GetConsole(nsISupports** aConsole)
+{
+  ErrorResult rv;
+  nsRefPtr<Console> console = GetConsole(rv);
+  console.forget(aConsole);
+  return rv.ErrorCode();
+}
+
+Console*
+nsGlobalWindow::GetConsole(ErrorResult& aRv)
+{
+  FORWARD_TO_INNER_OR_THROW(GetConsole, (aRv), aRv, nullptr);
+
+  if (!mConsole) {
+    mConsole = new Console(this);
+  }
+
+  return mConsole;
+}
+
 #ifdef MOZ_B2G
 void
 nsGlobalWindow::EnableNetworkEvent(uint32_t aType)
 {
   MOZ_ASSERT(IsInnerWindow());
 
   nsCOMPtr<nsIPermissionManager> permMgr =
     do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -101,16 +101,17 @@ struct nsIntSize;
 struct nsRect;
 
 class nsWindowSizes;
 
 namespace mozilla {
 class Selection;
 namespace dom {
 class BarProp;
+class Console;
 class Function;
 class Gamepad;
 class MediaQueryList;
 class Navigator;
 class SpeechSynthesis;
 class WakeLock;
 namespace indexedDB {
 class IDBFactory;
@@ -815,16 +816,18 @@ public:
   mozilla::dom::Element* GetFrameElement(mozilla::ErrorResult& aError);
   already_AddRefed<nsIDOMWindow> Open(const nsAString& aUrl,
                                       const nsAString& aName,
                                       const nsAString& aOptions,
                                       mozilla::ErrorResult& aError);
   mozilla::dom::Navigator* GetNavigator(mozilla::ErrorResult& aError);
   nsIDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
 
+  mozilla::dom::Console* GetConsole(mozilla::ErrorResult& aRv);
+
 protected:
   bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
                       mozilla::ErrorResult& aError);
 
 public:
   void Alert(const nsAString& aMessage, mozilla::ErrorResult& aError);
   bool Confirm(const nsAString& aMessage, mozilla::ErrorResult& aError);
   void Prompt(const nsAString& aMessage, const nsAString& aInitial,
@@ -1449,16 +1452,17 @@ protected:
   nsRefPtr<mozilla::dom::BarProp> mPersonalbar;
   nsRefPtr<mozilla::dom::BarProp> mStatusbar;
   nsRefPtr<mozilla::dom::BarProp> mScrollbars;
   nsRefPtr<nsDOMWindowUtils>    mWindowUtils;
   nsString                      mStatus;
   nsString                      mDefaultStatus;
   nsGlobalWindowObserver*       mObserver; // Inner windows only.
   nsCOMPtr<nsIDOMCrypto>        mCrypto;
+  nsRefPtr<mozilla::dom::Console> mConsole;
 
   nsCOMPtr<nsIDOMStorage>      mLocalStorage;
   nsCOMPtr<nsIDOMStorage>      mSessionStorage;
 
   nsCOMPtr<nsIXPConnectJSObjectHolder> mInnerWindowHolder;
 
   // These member variable are used only on inner windows.
   nsRefPtr<nsEventListenerManager> mListenerManager;
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -5,16 +5,17 @@ support-files =
   iframe_messageChannel_post.html
   file_empty.html
   iframe_postMessage_solidus.html
 
 [test_Image_constructor.html]
 [test_appname_override.html]
 [test_bug913761.html]
 [test_clearTimeoutIntervalNoArg.html]
+[test_consoleEmptyStack.html]
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_document.all_unqualified.html]
 [test_domcursor.html]
 [test_domrequest.html]
 [test_e4x_for_each.html]
 [test_error.html]
 [test_gsp-qualified.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_consoleEmptyStack.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Test for empty stack in console</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+window.setTimeout(console.log.bind(console), 0, "xyz");
+
+window.addEventListener("fake", console.log.bind(console, "xyz"));
+
+window.addEventListener("fake", function() {
+  ok(true, "Still alive");
+  SimpleTest.finish();
+});
+
+window.dispatchEvent(new Event("fake"));
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -240,16 +240,20 @@ DOMInterfaces = {
 'CommandEvent': {
     'nativeType': 'mozilla::dom::CommandEvent',
 },
 
 'CompositionEvent': {
     'nativeType': 'mozilla::dom::CompositionEvent',
 },
 
+'Console': {
+    'implicitJSContext': [ 'trace', 'time', 'timeEnd' ],
+},
+
 'ConvolverNode': {
     'implicitJSContext': [ 'buffer' ],
     'resultNotAddRefed': [ 'buffer' ],
 },
 
 'Coordinates': {
     'headerFile': 'nsGeoPosition.h'
 },
--- a/dom/interfaces/base/nsIDOMWindow.idl
+++ b/dom/interfaces/base/nsIDOMWindow.idl
@@ -19,17 +19,17 @@ interface nsIVariant;
  * The nsIDOMWindow interface is the primary interface for a DOM
  * window object. It represents a single window object that may
  * contain child windows if the document in the window contains a
  * HTML frameset document or if the document contains iframe elements.
  *
  * @see <http://www.whatwg.org/html/#window>
  */
 
-[scriptable, uuid(97b6784b-ab12-4f79-8422-d7868a4cc7dc)]
+[scriptable, uuid(8c115ab3-cf96-492c-850c-3b18056b45e2)]
 interface nsIDOMWindow : nsISupports
 {
   // the current browsing context
   readonly attribute nsIDOMWindow                       window;
 
   /* [replaceable] self */
   readonly attribute nsIDOMWindow                       self;
 
@@ -499,16 +499,21 @@ interface nsIDOMWindow : nsISupports
   [implicit_jscontext] attribute jsval ondevicemotion;
   [implicit_jscontext] attribute jsval ondeviceorientation;
   [implicit_jscontext] attribute jsval ondeviceproximity;
   [implicit_jscontext] attribute jsval onuserproximity;
   [implicit_jscontext] attribute jsval ondevicelight;
 
   [implicit_jscontext] attribute jsval onmouseenter;
   [implicit_jscontext] attribute jsval onmouseleave;
+
+  /**
+   * Console API
+   */
+  readonly attribute nsISupports console;
 };
 
 [scriptable, uuid(2146c906-57f7-486c-a1b4-8cdb57ef577f)]
 interface nsIDOMWindowPerformance : nsISupports
 {
   /**
    * A namespace to hold performance related data and statistics.
    */
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Console.webidl
@@ -0,0 +1,75 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+[ChromeOnly]
+interface Console {
+  void log(any... data);
+  void info(any... data);
+  void warn(any... data);
+  void error(any... data);
+  void _exception(any... data);
+  void debug(any... data);
+  void trace();
+  void dir(any... data);
+  void group(any... data);
+  void groupCollapsed(any... data);
+  void groupEnd(any... data);
+  void time(any time);
+  void timeEnd(any time);
+
+  [Throws]
+  void profile(any... data);
+
+  [Throws]
+  void profileEnd(any... data);
+
+  void assert(boolean condition, any... data);
+  void ___noSuchMethod__();
+};
+
+// This is used to propagate console events to the observers.
+dictionary ConsoleEvent {
+  (unsigned long or DOMString) ID;
+  (unsigned long or DOMString) innerID;
+  DOMString level = "";
+  DOMString filename = "";
+  unsigned long lineNumber = 0;
+  DOMString functionName = "";
+  double timeStamp = 0;
+  sequence<any> arguments;
+  boolean private = false;
+  sequence<ConsoleStackEntry> stacktrace;
+  DOMString groupName = "";
+  any timer = null;
+};
+
+// Event for profile operations
+dictionary ConsoleProfileEvent {
+  DOMString action = "";
+  sequence<any> arguments;
+};
+
+// This dictionary is used to manage stack trace data.
+dictionary ConsoleStackEntry {
+  DOMString filename = "";
+  unsigned long lineNumber = 0;
+  DOMString functionName = "";
+  unsigned long language = 0;
+};
+
+dictionary ConsoleTimerStart {
+  DOMString name = "";
+  double started = 0;
+};
+
+dictionary ConsoleTimerEnd {
+  DOMString name = "";
+  double duration = 0;
+};
+
+dictionary ConsoleTimerError {
+  DOMString error = "maxTimersExceeded";
+};
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -337,16 +337,23 @@ partial interface Window {
 
   [ChromeOnly, Throws] readonly attribute object? __content;
 };
 
 Window implements TouchEventHandlers;
 
 Window implements OnErrorEventHandlerForWindow;
 
+// ConsoleAPI
+partial interface Window {
+  [Replaceable, GetterThrows]
+  readonly attribute Console console;
+};
+
+
 [ChromeOnly] interface ChromeWindow {
   [Func="nsGlobalWindow::IsChromeWindow"]
   const unsigned short STATE_MAXIMIZED = 1;
   [Func="nsGlobalWindow::IsChromeWindow"]
   const unsigned short STATE_MINIMIZED = 2;
   [Func="nsGlobalWindow::IsChromeWindow"]
   const unsigned short STATE_NORMAL = 3;
   [Func="nsGlobalWindow::IsChromeWindow"]
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -50,16 +50,17 @@ WEBIDL_FILES = [
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
     'ChildNode.webidl',
     'ClipboardEvent.webidl',
     'CommandEvent.webidl',
     'Comment.webidl',
     'CompositionEvent.webidl',
+    'Console.webidl',
     'Contacts.webidl',
     'ConvolverNode.webidl',
     'Coordinates.webidl',
     'CSS.webidl',
     'CSSPrimitiveValue.webidl',
     'CSSStyleDeclaration.webidl',
     'CSSStyleSheet.webidl',
     'CSSValue.webidl',
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -102,17 +102,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   });
 });
 
 [
 #ifdef MOZ_WEBRTC
   ["WebrtcUI", ["getUserMedia:request", "recording-device-events"], "chrome://browser/content/WebrtcUI.js"],
 #endif
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
-  ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
 ].forEach(function (aScript) {
   let [name, notifications, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -38,17 +38,16 @@ chrome.jar:
   content/netError.xhtml               (content/netError.xhtml)
   content/SelectHelper.js              (content/SelectHelper.js)
   content/SelectionHandler.js          (content/SelectionHandler.js)
   content/dbg-browser-actors.js        (content/dbg-browser-actors.js)
 * content/WebappRT.js                  (content/WebappRT.js)
   content/InputWidgetHelper.js         (content/InputWidgetHelper.js)
   content/WebrtcUI.js                  (content/WebrtcUI.js)
   content/MemoryObserver.js            (content/MemoryObserver.js)
-  content/ConsoleAPI.js                (content/ConsoleAPI.js)
   content/PluginHelper.js              (content/PluginHelper.js)
   content/OfflineApps.js               (content/OfflineApps.js)
   content/MasterPassword.js            (content/MasterPassword.js)
   content/FindHelper.js                (content/FindHelper.js)
   content/PermissionsHelper.js         (content/PermissionsHelper.js)
   content/FeedHandler.js               (content/FeedHandler.js)
   content/Feedback.js                  (content/Feedback.js)
   content/Linkify.js                   (content/Linkify.js)
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -275,17 +275,16 @@
 @BINPATH@/components/xulapp.xpt
 @BINPATH@/components/xul.xpt
 @BINPATH@/components/xuldoc.xpt
 @BINPATH@/components/xultmpl.xpt
 @BINPATH@/components/zipwriter.xpt
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
-@BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/ConsoleAPIStorage.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
 @BINPATH@/components/NotificationStorage.js
 @BINPATH@/components/NotificationStorage.manifest
 @BINPATH@/components/SettingsManager.js
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -924,17 +924,17 @@ BrowserTabActor.prototype = {
    *        The window object you want to check.
    * @return boolean
    *         True if the window.console object is native, or false otherwise.
    */
   hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) {
     let isNative = false;
     try {
       let console = aWindow.wrappedJSObject.console;
-      isNative = "__mozillaConsole__" in console;
+      isNative = console instanceof aWindow.Console;
     }
     catch (ex) { }
     return isNative;
   }
 };
 
 /**
  * The request types this actor can handle.