Bug 1425574 - Fill the feature gap between Console.jsm and Console API - part 4 - dump function, r=smaug, r=bgrins
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 04 Jan 2018 19:19:44 +0100
changeset 449551 465b929da94d51a1224dd0972432d6a2cf783b30
parent 449550 1bd2d2b4d9e628a9d4c7376787da289e2f50abb9
child 449552 69dc8f8bea227722540cab47773f97a4fbb49e04
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, bgrins
bugs1425574
milestone59.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 1425574 - Fill the feature gap between Console.jsm and Console API - part 4 - dump function, r=smaug, r=bgrins
dom/console/Console.cpp
dom/console/Console.h
dom/console/ConsoleInstance.cpp
dom/console/tests/console.jsm
dom/console/tests/test_jsm.xul
dom/webidl/Console.webidl
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -766,22 +766,24 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
 // We don't need to traverse/unlink mStorage and mSandbox because they are not
 // CCed objects and they are only used on the main thread, even when this
 // Console object is used on workers.
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
   tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
   for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
     tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
   }
 
   for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
@@ -812,16 +814,17 @@ Console::Create(nsPIDOMWindowInner* aWin
 
   return console.forget();
 }
 
 Console::Console(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
   , mOuterID(0)
   , mInnerID(0)
+  , mDumpToStdout(false)
   , mStatus(eUnknown)
   , mCreationTimeStamp(TimeStamp::Now())
 {
   if (mWindow) {
     mInnerID = mWindow->WindowID();
 
     // Without outerwindow any console message coming from this object will not
     // shown in the devtools webconsole. But this should be fine because
@@ -1069,16 +1072,18 @@ void
 Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
                                const Sequence<JS::Value>& aData)
 {
   // Make all Console API no-op if DevTools aren't enabled.
   if (!nsContentUtils::DevToolsEnabled(aCx)) {
     return;
   }
 
+  MaybeExecuteDumpFunction(aCx, aAction, aData);
+
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     RefPtr<ConsoleProfileRunnable> runnable =
       new ConsoleProfileRunnable(this, aAction, aData);
 
     runnable->Dispatch(aCx);
     return;
   }
@@ -1221,16 +1226,17 @@ void
 Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
                         const nsAString& aMethodString,
                         const Sequence<JS::Value>& aData)
 {
   // Make all Console API no-op if DevTools aren't enabled.
   if (!nsContentUtils::DevToolsEnabled(aCx)) {
     return;
   }
+
   AssertIsOnOwningThread();
 
   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
   if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
                                        aData, this))) {
@@ -1331,16 +1337,24 @@ Console::MethodInternal(JSContext* aCx, 
 
   else if (aMethodName == MethodCount) {
     callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
     if (!callData->mCountValue) {
       return;
     }
   }
 
+  // Before processing this CallData differently, it's time to call the dump
+  // function.
+  if (aMethodName == MethodTrace) {
+    MaybeExecuteDumpFunctionForTrace(aCx, stack);
+  } else {
+    MaybeExecuteDumpFunction(aCx, aMethodString, aData);
+  }
+
   if (NS_IsMainThread()) {
     if (mWindow) {
       callData->SetIDs(mOuterID, mInnerID);
     } else if (!mPassedInnerID.IsEmpty()) {
       callData->SetIDs(NS_LITERAL_STRING("jsm"), mPassedInnerID);
     } else {
       nsAutoString filename;
       if (callData->mTopStackFrame.isSome()) {
@@ -2558,10 +2572,115 @@ Console::MonotonicTimer(JSContext* aCx, 
 /* static */ already_AddRefed<ConsoleInstance>
 Console::CreateInstance(const GlobalObject& aGlobal,
                         const ConsoleInstanceOptions& aOptions)
 {
   RefPtr<ConsoleInstance> console = new ConsoleInstance(aOptions);
   return console.forget();
 }
 
+void
+Console::MaybeExecuteDumpFunction(JSContext* aCx,
+                                  const nsAString& aMethodName,
+                                  const Sequence<JS::Value>& aData)
+{
+  if (!mDumpFunction && !mDumpToStdout) {
+    return;
+  }
+
+  nsAutoString message;
+  message.AssignLiteral("console.");
+  message.Append(aMethodName);
+  message.AppendLiteral(": ");
+
+  for (uint32_t i = 0; i < aData.Length(); ++i) {
+    JS::Rooted<JS::Value> v(aCx, aData[i]);
+    JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
+    if (!jsString) {
+      continue;
+    }
+
+    nsAutoJSString string;
+    if (NS_WARN_IF(!string.init(aCx, jsString))) {
+      return;
+    }
+
+    if (i != 0) {
+      message.AppendLiteral(" ");
+    }
+
+    message.Append(string);
+  }
+
+  message.AppendLiteral("\n");
+  ExecuteDumpFunction(message);
+}
+
+void
+Console::MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack)
+{
+  if (!aStack || (!mDumpFunction && !mDumpToStdout)) {
+    return;
+  }
+
+  nsAutoString message;
+  message.AssignLiteral("console.trace:\n");
+
+  nsCOMPtr<nsIStackFrame> stack(aStack);
+
+  while (stack) {
+    nsAutoString filename;
+    nsresult rv = stack->GetFilename(aCx, filename);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.Append(filename);
+    message.AppendLiteral(" ");
+
+    int32_t lineNumber;
+    rv = stack->GetLineNumber(aCx, &lineNumber);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.AppendInt(lineNumber);
+    message.AppendLiteral(" ");
+
+    nsAutoString functionName;
+    rv = stack->GetName(aCx, functionName);
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    message.Append(filename);
+    message.AppendLiteral("\n");
+
+    nsCOMPtr<nsIStackFrame> caller;
+    rv = stack->GetCaller(aCx, getter_AddRefs(caller));
+    NS_ENSURE_SUCCESS_VOID(rv);
+
+    if (!caller) {
+      rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
+      NS_ENSURE_SUCCESS_VOID(rv);
+    }
+
+    stack.swap(caller);
+  }
+
+  message.AppendLiteral("\n");
+  ExecuteDumpFunction(message);
+}
+
+void
+Console::ExecuteDumpFunction(const nsAString& aMessage)
+{
+  if (mDumpFunction) {
+    IgnoredErrorResult rv;
+    mDumpFunction->Call(aMessage, rv);
+    return;
+  }
+
+  NS_ConvertUTF16toUTF8 str(aMessage);
+  MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
+#ifdef ANDROID
+  __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
+#endif
+  fputs(str.get(), stdout);
+  fflush(stdout);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/console/Console.h
+++ b/dom/console/Console.h
@@ -16,23 +16,25 @@
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsPIDOMWindow.h"
 
 class nsIConsoleAPIStorage;
 class nsIPrincipal;
+class nsIStackFrame;
 
 namespace mozilla {
 namespace dom {
 
 class AnyCallback;
 class ConsoleCallData;
 class ConsoleInstance;
+class ConsoleInstanceDumpCallback;
 class ConsoleRunnable;
 class ConsoleCallDataRunnable;
 class ConsoleProfileRunnable;
 struct ConsoleInstanceOptions;
 struct ConsoleTimerError;
 struct ConsoleStackEntry;
 
 class Console final : public nsIObserver
@@ -381,16 +383,26 @@ private:
   bool
   IsShuttingDown() const;
 
   bool
   MonotonicTimer(JSContext* aCx, MethodName aMethodName,
                  const Sequence<JS::Value>& aData,
                  DOMHighResTimeStamp* aTimeStamp);
 
+  void
+  MaybeExecuteDumpFunction(JSContext* aCx, const nsAString& aMethodName,
+                           const Sequence<JS::Value>& aData);
+
+  void
+  MaybeExecuteDumpFunctionForTrace(JSContext* aCx, nsIStackFrame* aStack);
+
+  void
+  ExecuteDumpFunction(const nsAString& aMessage);
+
   // All these nsCOMPtr are touched on main thread only.
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
   RefPtr<JSObjectHolder> mSandbox;
 
   // Touched on the owner thread.
   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
@@ -414,16 +426,18 @@ private:
   nsTArray<nsString> mGroupStack;
 
   uint64_t mOuterID;
   uint64_t mInnerID;
 
   // Set only by ConsoleInstance:
   nsString mConsoleID;
   nsString mPassedInnerID;
+  RefPtr<ConsoleInstanceDumpCallback> mDumpFunction;
+  bool mDumpToStdout;
 
   enum {
     eUnknown,
     eInitialized,
     eShuttingDown
   } mStatus;
 
   // This is used when Console is created and it's used only for JSM custom
--- a/dom/console/ConsoleInstance.cpp
+++ b/dom/console/ConsoleInstance.cpp
@@ -19,16 +19,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
 NS_INTERFACE_MAP_END
 
 ConsoleInstance::ConsoleInstance(const ConsoleInstanceOptions& aOptions)
   : mConsole(new Console(nullptr))
 {
   mConsole->mConsoleID = aOptions.mConsoleID;
   mConsole->mPassedInnerID = aOptions.mInnerID;
+
+  if (aOptions.mDump.WasPassed()) {
+    mConsole->mDumpFunction = &aOptions.mDump.Value();
+  } else {
+    // For historical reasons, ConsoleInstance prints messages on stdout.
+    mConsole->mDumpToStdout = true;
+  }
 }
 
 ConsoleInstance::~ConsoleInstance()
 {}
 
 JSObject*
 ConsoleInstance::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
--- a/dom/console/tests/console.jsm
+++ b/dom/console/tests/console.jsm
@@ -1,16 +1,21 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 this.EXPORTED_SYMBOLS = [ "ConsoleTest" ];
 
 this.ConsoleTest = {
-  go: function() {
+  go: function(dumpFunction) {
     console.log("Hello world!");
     console.createInstance().log("Hello world!");
-    console.createInstance({
+
+    let c = console.createInstance({
       consoleID: "wow",
       innerID: "CUSTOM INNER",
-    }).log("Hello world!");
+      dump: dumpFunction,
+    });
+
+    c.log("Hello world!");
+    c.trace("Hello world!");
   }
 };
--- a/dom/console/tests/test_jsm.xul
+++ b/dom/console/tests/test_jsm.xul
@@ -12,54 +12,61 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
 const JSM = "chrome://mochitests/content/chrome/dom/console/tests/console.jsm";
 
+let dumpCalled = 0;
+function dumpFunction(msg) {
+  dump("Message received: " + msg); // Just for debugging
+  dumpCalled++;
+}
+
 function consoleListener() {
   SpecialPowers.addObserver(this, "console-api-log-event");
 }
 
 consoleListener.prototype  = {
   count: 0,
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "console-api-log-event") {
       var obj = aSubject.wrappedJSObject;
       if (obj.innerID == JSM) {
         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
-        is (obj.arguments[0], "Hello world!", "Message matches");
+        is(obj.arguments[0], "Hello world!", "Message matches");
         is(obj.consoleID, "", "No consoleID for console API");
 
         // We want to see 2 messages from this innerID, the first is generated
         // by console.log, the second one from createInstance().log();
         ++this.count;
       } else if (obj.innerID = "CUSTOM INNER") {
         is(obj.ID, "jsm", "ID and InnerID are correctly set.");
-        is (obj.arguments[0], "Hello world!", "Message matches");
+        is(obj.arguments[0], "Hello world!", "Message matches");
         is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
         ++this.count;
       }
 
-      if (this.count == 3) {
+      if (this.count == 4) {
+        is(dumpCalled, 2, "Dump has been called!");
         SpecialPowers.removeObserver(this, "console-api-log-event");
         SimpleTest.finish();
       }
     }
   }
 }
 function test() {
   SimpleTest.waitForExplicitFinish();
 
   var cl = new consoleListener();
   Components.utils.import(JSM);
-  ConsoleTest.go();
+  ConsoleTest.go(dumpFunction);
 }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
   </body>
 </window>
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -149,17 +149,19 @@ interface ConsoleInstance {
 
   void _exception(any... data);
   void timeStamp(optional any data);
 
   void profile(any... data);
   void profileEnd(any... data);
 };
 
+callback ConsoleInstanceDumpCallback = void (DOMString message);
+
 dictionary ConsoleInstanceOptions {
+  ConsoleInstanceDumpCallback dump;
 /* TODO:
-  boolean dump = false;
   DOMString prefix = "";
   DOMString maxLogLevel = "";
 */
   DOMString innerID = "";
   DOMString consoleID = "";
 };