Backed out changesets df5a68af3d30 and 6acacaa75fad (bug 995295) for bustage.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 16 Apr 2014 15:46:18 -0400
changeset 197340 8a13590ce482280ea38a5c886e9812d918ed6848
parent 197339 c60b059c47ca40af1e2f21aaaa23da263ed0a39b
child 197341 0b3a14bbfd81231756c88255ea3563a0760a2acc
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs995295
milestone31.0a1
backs outdf5a68af3d309f3f5aef99777852d3d6d73acc9f
6acacaa75fadf036b43906fe9cf6c4840f540dc8
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
Backed out changesets df5a68af3d30 and 6acacaa75fad (bug 995295) for bustage. CLOSED TREE
dom/base/Console.cpp
dom/webidl/Console.webidl
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -2,28 +2,25 @@
 /* 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 "mozilla/dom/ToJSValue.h"
-#include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
 #include "nsPerformance.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "xpcprivate.h"
-#include "nsContentUtils.h"
 
 #include "nsIConsoleAPIStorage.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsIServiceManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWebNavigation.h"
@@ -148,44 +145,38 @@ public:
   }
 
   ~ConsoleCallData()
   {
     MOZ_COUNT_DTOR(ConsoleCallData);
   }
 
   void
-  Initialize(Console::MethodName aName,
+  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;
-
-  // 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
-  //     we're created on main thread.
-  Maybe<ConsoleStackEntry> mTopStackFrame;
-  Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
-  nsCOMPtr<nsIStackFrame> mStack;
+  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)
@@ -211,29 +202,31 @@ public:
   }
 
   virtual
   ~ConsoleRunnable()
   {
   }
 
   bool
-  Dispatch(JSContext* aCx)
+  Dispatch()
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
 
-    if (!PreDispatch(aCx)) {
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+
+    if (!PreDispatch(cx)) {
       return false;
     }
 
     AutoSyncLoopHolder syncLoop(mWorkerPrivate);
     mSyncLoopTarget = syncLoop.EventTarget();
 
     if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
-      JS_ReportError(aCx,
+      JS_ReportError(cx,
                      "Failed to dispatch to main thread for the Console API!");
       return false;
     }
 
     return syncLoop.Run();
   }
 
 private:
@@ -277,16 +270,17 @@ public:
   {
   }
 
 private:
   bool
   PreDispatch(JSContext* aCx) MOZ_OVERRIDE
   {
     ClearException ce(aCx);
+    JSAutoCompartment ac(aCx, mCallData->mGlobal);
 
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mCallData->mArguments.Length()));
     if (!arguments) {
       return false;
     }
 
     for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) {
@@ -298,16 +292,17 @@ 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;
     return true;
   }
 
   void
   RunConsole() MOZ_OVERRIDE
   {
     // Walk up to our containing page
     WorkerPrivate* wp = mWorkerPrivate;
@@ -352,16 +347,17 @@ private:
         return;
       }
 
       mCallData->mArguments.AppendElement(value);
     }
 
     MOZ_ASSERT(mCallData->mArguments.Length() == length);
 
+    mCallData->mGlobal = JS::CurrentGlobalOrNull(cx);
     console->AppendCallData(mCallData.forget());
   }
 
 private:
   nsAutoPtr<ConsoleCallData> mCallData;
 
   JSAutoStructuredCloneBuffer mArguments;
   nsTArray<nsString> mStrings;
@@ -501,16 +497,20 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   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)
@@ -667,17 +667,17 @@ void
 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
                        const Sequence<JS::Value>& aData,
                        ErrorResult& aRv)
 {
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     nsRefPtr<ConsoleProfileRunnable> runnable =
       new ConsoleProfileRunnable(aAction, aData);
-    runnable->Dispatch(aCx);
+    runnable->Dispatch();
     return;
   }
 
   RootedDictionary<ConsoleProfileEvent> event(aCx);
   event.mAction = aAction;
 
   event.mArguments.Construct();
   Sequence<JS::Value>& sequence = event.mArguments.Value();
@@ -728,68 +728,16 @@ Console::Assert(JSContext* aCx, bool aCo
 METHOD(Count, "count")
 
 void
 Console::__noSuchMethod__()
 {
   // Nothing to do.
 }
 
-static
-nsresult
-StackFrameToStackEntry(nsIStackFrame* aStackFrame,
-                       ConsoleStackEntry& aStackEntry,
-                       uint32_t aLanguage)
-{
-  MOZ_ASSERT(aStackFrame);
-
-  nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  int32_t lineNumber;
-  rv = aStackFrame->GetLineNumber(&lineNumber);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aStackEntry.mLineNumber = lineNumber;
-
-  rv = aStackFrame->GetName(aStackEntry.mFunctionName);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aStackEntry.mLanguage = aLanguage;
-  return NS_OK;
-}
-
-static
-nsresult
-ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
-{
-  nsCOMPtr<nsIStackFrame> stack(aStack);
-
-  while (stack) {
-    uint32_t language;
-    nsresult rv = stack->GetLanguage(&language);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (language == nsIProgrammingLanguage::JAVASCRIPT ||
-        language == nsIProgrammingLanguage::JAVASCRIPT2) {
-      ConsoleStackEntry& data = *aRefiedStack.AppendElement();
-      rv = StackFrameToStackEntry(stack, data, language);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    nsCOMPtr<nsIStackFrame> caller;
-    rv = stack->GetCaller(getter_AddRefs(caller));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    stack.swap(caller);
-  }
-
-  return NS_OK;
-}
-
 // 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.
@@ -819,17 +767,17 @@ Console::Method(JSContext* aCx, MethodNa
   private:
     LinkedList<ConsoleCallData>& mList;
     bool mUnfinished;
   };
 
   ConsoleCallData* callData = new ConsoleCallData();
   mQueuedCalls.insertBack(callData);
 
-  callData->Initialize(aMethodName, aMethodString, aData);
+  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;
     }
@@ -844,62 +792,64 @@ Console::Method(JSContext* aCx, MethodNa
                       DEFAULT_MAX_STACKTRACE_DEPTH : 1;
   nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
 
   if (!stack) {
     Throw(aCx, NS_ERROR_FAILURE);
     return;
   }
 
-  // Walk up to the first JS stack frame and save it if we find it.
+  // 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) {
-      callData->mTopStackFrame.construct();
-      nsresult rv = StackFrameToStackEntry(stack,
-                                           callData->mTopStackFrame.ref(),
-                                           language);
+      ConsoleStackEntry& data = *callData->mStack.AppendElement();
+
+      rv = stack->GetFilename(data.mFilename);
       if (NS_FAILED(rv)) {
         Throw(aCx, rv);
         return;
       }
 
-      break;
+      int32_t lineNumber;
+      rv = stack->GetLineNumber(&lineNumber);
+      if (NS_FAILED(rv)) {
+        Throw(aCx, rv);
+        return;
+      }
+
+      data.mLineNumber = lineNumber;
+
+      rv = stack->GetName(data.mFunctionName);
+      if (NS_FAILED(rv)) {
+        Throw(aCx, rv);
+        return;
+      }
+
+      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);
 
-  if (NS_IsMainThread()) {
-    callData->mStack = stack;
-  } else {
-    // nsIStackFrame is not threadsafe, so we need to snapshot it now,
-    // before we post our runnable to the main thread.
-    callData->mReifiedStack.construct();
-    nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      Throw(aCx, rv);
-      return;
-    }
-  }
-
   // 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()) {
@@ -916,17 +866,17 @@ Console::Method(JSContext* aCx, MethodNa
   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(aCx);
+    runnable->Dispatch();
     return;
   }
 
   if (!mTimer) {
     mTimer = do_CreateInstance("@mozilla.org/timer;1");
     mTimer->InitWithCallback(this, CALL_DELAY,
                              nsITimer::TYPE_REPEATING_SLACK);
   }
@@ -963,87 +913,31 @@ Console::Notify(nsITimer *timer)
   if (mQueuedCalls.isEmpty() && mTimer) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
 
   return NS_OK;
 }
 
-// We store information to lazily compute the stack in the reserved slots of
-// LazyStackGetter.  The first slot always stores a JS object: it's either the
-// JS wrapper of the nsIStackFrame or the actual reified stack representation.
-// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
-// reified the stack yet, or an UndefinedValue() otherwise.
-enum {
-  SLOT_STACKOBJ,
-  SLOT_RAW_STACK
-};
-
-bool
-LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
-{
-  JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
-  JS::Rooted<JSObject*> callee(aCx, &args.callee());
-
-  JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
-  if (v.isUndefined()) {
-    // Already reified.
-    args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
-    return true;
-  }
-
-  nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
-  nsTArray<ConsoleStackEntry> reifiedStack;
-  nsresult rv = ReifyStack(stack, reifiedStack);
-  if (NS_FAILED(rv)) {
-    Throw(aCx, rv);
-    return false;
-  }
-
-  JS::Rooted<JS::Value> stackVal(aCx);
-  if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
-    return false;
-  }
-
-  MOZ_ASSERT(stackVal.isObject());
-
-  js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
-  js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
-
-  args.rval().set(stackVal);
-  return true;
-}
-
 void
 Console::ProcessCallData(ConsoleCallData* aData)
 {
   MOZ_ASSERT(aData);
-  MOZ_ASSERT(NS_IsMainThread());
 
   ConsoleStackEntry frame;
-  if (!aData->mTopStackFrame.empty()) {
-    frame = aData->mTopStackFrame.ref();
+  if (!aData->mStack.IsEmpty()) {
+    frame = aData->mStack[0];
   }
 
   AutoSafeJSContext cx;
   ClearException ce(cx);
   RootedDictionary<ConsoleEvent> event(cx);
 
-  // We want to create a console event object and pass it to our
-  // nsIConsoleAPIStorage implementation.  We want to define some accessor
-  // properties on this object, and those will need to keep an nsIStackFrame
-  // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
-  // further, passing untrusted objects to system code is likely to run afoul of
-  // Object Xrays.  So we want to wrap in a system-principal scope here.  But
-  // which one?  We could cheat and try to get the underlying JSObject* of
-  // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
-  // with explicit permission from the XPConnect module owner.  If you're
-  // tempted to do that anywhere else, talk to said module owner first.
-  JSAutoCompartment ac(cx, xpc::GetJunkScope());
+  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.
@@ -1072,19 +966,24 @@ Console::ProcessCallData(ConsoleCallData
                        event.mStyles.Value());
       break;
 
     default:
       event.mArguments.Construct();
       ArgumentsToValueList(aData->mArguments, event.mArguments.Value());
   }
 
-  if (aData->mMethodName == MethodGroup ||
-      aData->mMethodName == MethodGroupCollapsed ||
-      aData->mMethodName == MethodGroupEnd) {
+  if (ShouldIncludeStackrace(aData->mMethodName)) {
+    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()) {
@@ -1103,60 +1002,16 @@ Console::ProcessCallData(ConsoleCallData
 
   JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
   MOZ_ASSERT(eventObj);
 
   if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
     return;
   }
 
-  if (ShouldIncludeStackrace(aData->mMethodName)) {
-    // Now define the "stacktrace" property on eventObj.  There are two cases
-    // here.  Either we came from a worker and have a reified stack, or we want
-    // to define a getter that will lazily reify the stack.
-    if (!aData->mReifiedStack.empty()) {
-      JS::Rooted<JS::Value> stacktrace(cx);
-      if (!ToJSValue(cx, aData->mReifiedStack.ref(), &stacktrace) ||
-          !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
-                             nullptr, nullptr, JSPROP_ENUMERATE)) {
-        return;
-      }
-    } else {
-      JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
-                                                    eventObj, "stacktrace");
-      if (!fun) {
-        return;
-      }
-
-      JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
-
-      // We want to store our stack in the function and have it stay alive.  But
-      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
-      // wrapper and the raw pointer: the former will keep the latter alive.
-      JS::Rooted<JS::Value> stackVal(cx);
-      nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
-                                               &stackVal);
-      if (NS_FAILED(rv)) {
-        return;
-      }
-
-      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
-      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
-                                    JS::PrivateValue(aData->mStack.get()));
-
-      if (!JS_DefineProperty(cx, eventObj, "stacktrace", JS::UndefinedValue(),
-                             JS_DATA_TO_FUNC_PTR(JSPropertyOp, funObj.get()),
-                             nullptr,
-                             JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
-                             JSPROP_SETTER)) {
-        return;
-      }
-    }
-  }
-
   if (!mStorage) {
     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
   }
 
   if (!mStorage) {
     NS_WARNING("Failed to get the ConsoleAPIStorage service.");
     return;
   }
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -42,22 +42,17 @@ dictionary ConsoleEvent {
   DOMString functionName = "";
   double timeStamp = 0;
   sequence<any> arguments;
 
   // This array will only hold strings or null elements.
   sequence<any> styles;
 
   boolean private = false;
-  // stacktrace is handled via a getter in some cases so we can construct it
-  // lazily.  Note that we're not making this whole thing an interface because
-  // consumers expect to see own properties on it, which would mean making the
-  // props unforgeable, which means lots of JSFunction allocations.  Maybe we
-  // should fix those consumers, of course....
-  // sequence<ConsoleStackEntry> stacktrace;
+  sequence<ConsoleStackEntry> stacktrace;
   DOMString groupName = "";
   any timer = null;
   any counter = null;
 };
 
 // Event for profile operations
 dictionary ConsoleProfileEvent {
   DOMString action = "";