michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/dom/Console.h" michael@0: #include "mozilla/dom/ConsoleBinding.h" michael@0: michael@0: #include "mozilla/dom/Exceptions.h" michael@0: #include "mozilla/dom/ToJSValue.h" michael@0: #include "mozilla/Maybe.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsDocument.h" michael@0: #include "nsDOMNavigationTiming.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsPerformance.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "WorkerRunnable.h" michael@0: #include "xpcprivate.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "nsIConsoleAPIStorage.h" michael@0: #include "nsIDOMWindowUtils.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsIWebNavigation.h" michael@0: michael@0: // The maximum allowed number of concurrent timers per page. michael@0: #define MAX_PAGE_TIMERS 10000 michael@0: michael@0: // The maximum allowed number of concurrent counters per page. michael@0: #define MAX_PAGE_COUNTERS 10000 michael@0: michael@0: // The maximum stacktrace depth when populating the stacktrace array used for michael@0: // console.trace(). michael@0: #define DEFAULT_MAX_STACKTRACE_DEPTH 200 michael@0: michael@0: // The console API methods are async and their action is executed later. This michael@0: // delay tells how much later. michael@0: #define CALL_DELAY 15 // milliseconds michael@0: michael@0: // This constant tells how many messages to process in a single timer execution. michael@0: #define MESSAGES_IN_INTERVAL 1500 michael@0: michael@0: // This tag is used in the Structured Clone Algorithm to move js values from michael@0: // worker thread to main thread michael@0: #define CONSOLE_TAG JS_SCTAG_USER_MIN michael@0: michael@0: using namespace mozilla::dom::exceptions; michael@0: using namespace mozilla::dom::workers; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: /** michael@0: * Console API in workers uses the Structured Clone Algorithm to move any value michael@0: * from the worker thread to the main-thread. Some object cannot be moved and, michael@0: * in these cases, we convert them to strings. michael@0: * It's not the best, but at least we are able to show something. michael@0: */ michael@0: michael@0: // This method is called by the Structured Clone Algorithm when some data has michael@0: // to be read. michael@0: static JSObject* michael@0: ConsoleStructuredCloneCallbacksRead(JSContext* aCx, michael@0: JSStructuredCloneReader* /* unused */, michael@0: uint32_t aTag, uint32_t aData, michael@0: void* aClosure) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (aTag != CONSOLE_TAG) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsTArray* strings = static_cast*>(aClosure); michael@0: MOZ_ASSERT(strings->Length() > aData); michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS::Rooted obj(aCx); michael@0: if (!JS_ValueToObject(aCx, value, &obj)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: // This method is called by the Structured Clone Algorithm when some data has michael@0: // to be written. michael@0: static bool michael@0: ConsoleStructuredCloneCallbacksWrite(JSContext* aCx, michael@0: JSStructuredCloneWriter* aWriter, michael@0: JS::Handle aObj, michael@0: void* aClosure) michael@0: { michael@0: JS::Rooted value(aCx, JS::ObjectOrNullValue(aObj)); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, value)); michael@0: if (!jsString) { michael@0: return false; michael@0: } michael@0: michael@0: nsDependentJSString string; michael@0: if (!string.init(aCx, jsString)) { michael@0: return false; michael@0: } michael@0: michael@0: nsTArray* strings = static_cast*>(aClosure); michael@0: michael@0: if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) { michael@0: return false; michael@0: } michael@0: michael@0: strings->AppendElement(string); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */, michael@0: uint32_t /* aErrorId */) michael@0: { michael@0: NS_WARNING("Failed to clone data for the Console API in workers."); michael@0: } michael@0: michael@0: JSStructuredCloneCallbacks gConsoleCallbacks = { michael@0: ConsoleStructuredCloneCallbacksRead, michael@0: ConsoleStructuredCloneCallbacksWrite, michael@0: ConsoleStructuredCloneCallbacksError michael@0: }; michael@0: michael@0: class ConsoleCallData MOZ_FINAL : public LinkedListElement michael@0: { michael@0: public: michael@0: ConsoleCallData() michael@0: : mMethodName(Console::MethodLog) michael@0: , mPrivate(false) michael@0: , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC) michael@0: , mMonotonicTimer(0) michael@0: { michael@0: MOZ_COUNT_CTOR(ConsoleCallData); michael@0: } michael@0: michael@0: ~ConsoleCallData() michael@0: { michael@0: MOZ_COUNT_DTOR(ConsoleCallData); michael@0: } michael@0: michael@0: void michael@0: Initialize(JSContext* aCx, Console::MethodName aName, michael@0: const nsAString& aString, const Sequence& aArguments) michael@0: { michael@0: mGlobal = JS::CurrentGlobalOrNull(aCx); michael@0: mMethodName = aName; michael@0: mMethodString = aString; michael@0: michael@0: for (uint32_t i = 0; i < aArguments.Length(); ++i) { michael@0: mArguments.AppendElement(aArguments[i]); michael@0: } michael@0: } michael@0: michael@0: JS::Heap mGlobal; michael@0: michael@0: Console::MethodName mMethodName; michael@0: bool mPrivate; michael@0: int64_t mTimeStamp; michael@0: DOMHighResTimeStamp mMonotonicTimer; michael@0: michael@0: nsString mMethodString; michael@0: nsTArray> mArguments; michael@0: michael@0: // Stack management is complicated, because we want to do it as michael@0: // lazily as possible. Therefore, we have the following behavior: michael@0: // 1) mTopStackFrame is initialized whenever we have any JS on the stack michael@0: // 2) mReifiedStack is initialized if we're created in a worker. michael@0: // 3) mStack is set (possibly to null if there is no JS on the stack) if michael@0: // we're created on main thread. michael@0: Maybe mTopStackFrame; michael@0: Maybe> mReifiedStack; michael@0: nsCOMPtr mStack; michael@0: }; michael@0: michael@0: // This class is used to clear any exception at the end of this method. michael@0: class ClearException michael@0: { michael@0: public: michael@0: ClearException(JSContext* aCx) michael@0: : mCx(aCx) michael@0: { michael@0: } michael@0: michael@0: ~ClearException() michael@0: { michael@0: JS_ClearPendingException(mCx); michael@0: } michael@0: michael@0: private: michael@0: JSContext* mCx; michael@0: }; michael@0: michael@0: class ConsoleRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: ConsoleRunnable() michael@0: : mWorkerPrivate(GetCurrentThreadWorkerPrivate()) michael@0: { michael@0: MOZ_ASSERT(mWorkerPrivate); michael@0: } michael@0: michael@0: virtual michael@0: ~ConsoleRunnable() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: Dispatch() michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: JSContext* cx = mWorkerPrivate->GetJSContext(); michael@0: michael@0: if (!PreDispatch(cx)) { michael@0: return false; michael@0: } michael@0: michael@0: AutoSyncLoopHolder syncLoop(mWorkerPrivate); michael@0: mSyncLoopTarget = syncLoop.EventTarget(); michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { michael@0: JS_ReportError(cx, michael@0: "Failed to dispatch to main thread for the Console API!"); michael@0: return false; michael@0: } michael@0: michael@0: return syncLoop.Run(); michael@0: } michael@0: michael@0: private: michael@0: NS_IMETHOD Run() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: RunConsole(); michael@0: michael@0: nsRefPtr response = michael@0: new MainThreadStopSyncLoopRunnable(mWorkerPrivate, michael@0: mSyncLoopTarget.forget(), michael@0: true); michael@0: if (!response->Dispatch(nullptr)) { michael@0: NS_WARNING("Failed to dispatch response!"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: protected: michael@0: virtual bool michael@0: PreDispatch(JSContext* aCx) = 0; michael@0: michael@0: virtual void michael@0: RunConsole() = 0; michael@0: michael@0: WorkerPrivate* mWorkerPrivate; michael@0: michael@0: private: michael@0: nsCOMPtr mSyncLoopTarget; michael@0: }; michael@0: michael@0: // This runnable appends a CallData object into the Console queue running on michael@0: // the main-thread. michael@0: class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable michael@0: { michael@0: public: michael@0: ConsoleCallDataRunnable(ConsoleCallData* aCallData) michael@0: : mCallData(aCallData) michael@0: { michael@0: } michael@0: michael@0: private: michael@0: bool michael@0: PreDispatch(JSContext* aCx) MOZ_OVERRIDE michael@0: { michael@0: ClearException ce(aCx); michael@0: JSAutoCompartment ac(aCx, mCallData->mGlobal); michael@0: michael@0: JS::Rooted arguments(aCx, michael@0: JS_NewArrayObject(aCx, mCallData->mArguments.Length())); michael@0: if (!arguments) { michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) { michael@0: if (!JS_DefineElement(aCx, arguments, i, mCallData->mArguments[i], michael@0: nullptr, nullptr, JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted value(aCx, JS::ObjectValue(*arguments)); michael@0: michael@0: if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) { michael@0: return false; michael@0: } michael@0: michael@0: mCallData->mArguments.Clear(); michael@0: mCallData->mGlobal = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: RunConsole() MOZ_OVERRIDE michael@0: { michael@0: // Walk up to our containing page michael@0: WorkerPrivate* wp = mWorkerPrivate; michael@0: while (wp->GetParent()) { michael@0: wp = wp->GetParent(); michael@0: } michael@0: michael@0: AutoPushJSContext cx(wp->ParentJSContext()); michael@0: ClearException ce(cx); michael@0: michael@0: nsPIDOMWindow* window = wp->GetWindow(); michael@0: NS_ENSURE_TRUE_VOID(window); michael@0: michael@0: nsRefPtr win = static_cast(window); michael@0: NS_ENSURE_TRUE_VOID(win); michael@0: michael@0: ErrorResult error; michael@0: nsRefPtr console = win->GetConsole(error); michael@0: if (error.Failed()) { michael@0: NS_WARNING("Failed to get console from the window."); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted argumentsValue(cx); michael@0: if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(argumentsValue.isObject()); michael@0: JS::Rooted argumentsObj(cx, &argumentsValue.toObject()); michael@0: MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj)); michael@0: michael@0: uint32_t length; michael@0: if (!JS_GetArrayLength(cx, argumentsObj, &length)) { michael@0: return; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: JS::Rooted value(cx); michael@0: michael@0: if (!JS_GetElement(cx, argumentsObj, i, &value)) { michael@0: return; michael@0: } michael@0: michael@0: mCallData->mArguments.AppendElement(value); michael@0: } michael@0: michael@0: MOZ_ASSERT(mCallData->mArguments.Length() == length); michael@0: michael@0: mCallData->mGlobal = JS::CurrentGlobalOrNull(cx); michael@0: console->AppendCallData(mCallData.forget()); michael@0: } michael@0: michael@0: private: michael@0: nsAutoPtr mCallData; michael@0: michael@0: JSAutoStructuredCloneBuffer mArguments; michael@0: nsTArray mStrings; michael@0: }; michael@0: michael@0: // This runnable calls ProfileMethod() on the console on the main-thread. michael@0: class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable michael@0: { michael@0: public: michael@0: ConsoleProfileRunnable(const nsAString& aAction, michael@0: const Sequence& aArguments) michael@0: : mAction(aAction) michael@0: , mArguments(aArguments) michael@0: { michael@0: } michael@0: michael@0: private: michael@0: bool michael@0: PreDispatch(JSContext* aCx) MOZ_OVERRIDE michael@0: { michael@0: ClearException ce(aCx); michael@0: michael@0: JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); michael@0: if (!global) { michael@0: return false; michael@0: } michael@0: michael@0: JSAutoCompartment ac(aCx, global); michael@0: michael@0: JS::Rooted arguments(aCx, michael@0: JS_NewArrayObject(aCx, mArguments.Length())); michael@0: if (!arguments) { michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mArguments.Length(); ++i) { michael@0: if (!JS_DefineElement(aCx, arguments, i, mArguments[i], nullptr, nullptr, michael@0: JSPROP_ENUMERATE)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: JS::Rooted value(aCx, JS::ObjectValue(*arguments)); michael@0: michael@0: if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: RunConsole() MOZ_OVERRIDE michael@0: { michael@0: // Walk up to our containing page michael@0: WorkerPrivate* wp = mWorkerPrivate; michael@0: while (wp->GetParent()) { michael@0: wp = wp->GetParent(); michael@0: } michael@0: michael@0: AutoPushJSContext cx(wp->ParentJSContext()); michael@0: ClearException ce(cx); michael@0: michael@0: JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); michael@0: NS_ENSURE_TRUE_VOID(global); michael@0: JSAutoCompartment ac(cx, global); michael@0: michael@0: nsPIDOMWindow* window = wp->GetWindow(); michael@0: NS_ENSURE_TRUE_VOID(window); michael@0: michael@0: nsRefPtr win = static_cast(window); michael@0: NS_ENSURE_TRUE_VOID(win); michael@0: michael@0: ErrorResult error; michael@0: nsRefPtr console = win->GetConsole(error); michael@0: if (error.Failed()) { michael@0: NS_WARNING("Failed to get console from the window."); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted argumentsValue(cx); michael@0: if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(argumentsValue.isObject()); michael@0: JS::Rooted argumentsObj(cx, &argumentsValue.toObject()); michael@0: MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj)); michael@0: michael@0: uint32_t length; michael@0: if (!JS_GetArrayLength(cx, argumentsObj, &length)) { michael@0: return; michael@0: } michael@0: michael@0: Sequence arguments; michael@0: michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: JS::Rooted value(cx); michael@0: michael@0: if (!JS_GetElement(cx, argumentsObj, i, &value)) { michael@0: return; michael@0: } michael@0: michael@0: arguments.AppendElement(value); michael@0: } michael@0: michael@0: console->ProfileMethod(cx, mAction, arguments); michael@0: } michael@0: michael@0: private: michael@0: nsString mAction; michael@0: Sequence mArguments; michael@0: michael@0: JSAutoStructuredCloneBuffer mBuffer; michael@0: nsTArray mStrings; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(Console) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: michael@0: tmp->ClearConsoleData(); michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: michael@0: for (ConsoleCallData* data = tmp->mQueuedCalls.getFirst(); data != nullptr; michael@0: data = data->getNext()) { michael@0: if (data->mGlobal) { michael@0: aCallbacks.Trace(&data->mGlobal, "data->mGlobal", aClosure); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < data->mArguments.Length(); ++i) { michael@0: aCallbacks.Trace(&data->mArguments[i], "data->mArguments[i]", aClosure); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(Console) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(Console) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsITimerCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: Console::Console(nsPIDOMWindow* aWindow) michael@0: : mWindow(aWindow) michael@0: , mOuterID(0) michael@0: , mInnerID(0) michael@0: { michael@0: if (mWindow) { michael@0: MOZ_ASSERT(mWindow->IsInnerWindow()); michael@0: mInnerID = mWindow->WindowID(); michael@0: michael@0: nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow(); michael@0: MOZ_ASSERT(outerWindow); michael@0: mOuterID = outerWindow->WindowID(); michael@0: } michael@0: michael@0: if (NS_IsMainThread()) { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(this, "inner-window-destroyed", false); michael@0: } michael@0: } michael@0: michael@0: SetIsDOMBinding(); michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: Console::~Console() michael@0: { michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: Console::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (strcmp(aTopic, "inner-window-destroyed")) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr wrapper = do_QueryInterface(aSubject); michael@0: NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); michael@0: michael@0: uint64_t innerID; michael@0: nsresult rv = wrapper->GetData(&innerID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (innerID == mInnerID) { michael@0: nsCOMPtr obs = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (obs) { michael@0: obs->RemoveObserver(this, "inner-window-destroyed"); michael@0: } michael@0: michael@0: ClearConsoleData(); michael@0: mTimerRegistry.Clear(); michael@0: michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: JSObject* michael@0: Console::WrapObject(JSContext* aCx) michael@0: { michael@0: return ConsoleBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: #define METHOD(name, string) \ michael@0: void \ michael@0: Console::name(JSContext* aCx, const Sequence& aData) \ michael@0: { \ michael@0: Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \ michael@0: } michael@0: michael@0: METHOD(Log, "log") michael@0: METHOD(Info, "info") michael@0: METHOD(Warn, "warn") michael@0: METHOD(Error, "error") michael@0: METHOD(Exception, "exception") michael@0: METHOD(Debug, "debug") michael@0: michael@0: void michael@0: Console::Trace(JSContext* aCx) michael@0: { michael@0: const Sequence data; michael@0: Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data); michael@0: } michael@0: michael@0: // Displays an interactive listing of all the properties of an object. michael@0: METHOD(Dir, "dir"); michael@0: michael@0: METHOD(Group, "group") michael@0: METHOD(GroupCollapsed, "groupCollapsed") michael@0: METHOD(GroupEnd, "groupEnd") michael@0: michael@0: void michael@0: Console::Time(JSContext* aCx, const JS::Handle aTime) michael@0: { michael@0: Sequence data; michael@0: SequenceRooter rooter(aCx, &data); michael@0: michael@0: if (!aTime.isUndefined()) { michael@0: data.AppendElement(aTime); michael@0: } michael@0: michael@0: Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); michael@0: } michael@0: michael@0: void michael@0: Console::TimeEnd(JSContext* aCx, const JS::Handle aTime) michael@0: { michael@0: Sequence data; michael@0: SequenceRooter rooter(aCx, &data); michael@0: michael@0: if (!aTime.isUndefined()) { michael@0: data.AppendElement(aTime); michael@0: } michael@0: michael@0: Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); michael@0: } michael@0: michael@0: void michael@0: Console::Profile(JSContext* aCx, const Sequence& aData) michael@0: { michael@0: ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData); michael@0: } michael@0: michael@0: void michael@0: Console::ProfileEnd(JSContext* aCx, const Sequence& aData) michael@0: { michael@0: ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData); michael@0: } michael@0: michael@0: void michael@0: Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, michael@0: const Sequence& aData) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: // Here we are in a worker thread. michael@0: nsRefPtr runnable = michael@0: new ConsoleProfileRunnable(aAction, aData); michael@0: runnable->Dispatch(); michael@0: return; michael@0: } michael@0: michael@0: ClearException ce(aCx); michael@0: michael@0: RootedDictionary event(aCx); michael@0: event.mAction = aAction; michael@0: michael@0: event.mArguments.Construct(); michael@0: Sequence& sequence = event.mArguments.Value(); michael@0: michael@0: for (uint32_t i = 0; i < aData.Length(); ++i) { michael@0: sequence.AppendElement(aData[i]); michael@0: } michael@0: michael@0: JS::Rooted eventValue(aCx); michael@0: if (!event.ToObject(aCx, &eventValue)) { michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted eventObj(aCx, &eventValue.toObject()); michael@0: MOZ_ASSERT(eventObj); michael@0: michael@0: if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, michael@0: JSPROP_ENUMERATE)) { michael@0: return; michael@0: } michael@0: michael@0: nsXPConnect* xpc = nsXPConnect::XPConnect(); michael@0: nsCOMPtr wrapper; michael@0: const nsIID& iid = NS_GET_IID(nsISupports); michael@0: michael@0: if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr obs = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (obs) { michael@0: obs->NotifyObservers(wrapper, "console-api-profiler", nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Console::Assert(JSContext* aCx, bool aCondition, michael@0: const Sequence& aData) michael@0: { michael@0: if (!aCondition) { michael@0: Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData); michael@0: } michael@0: } michael@0: michael@0: METHOD(Count, "count") michael@0: michael@0: void michael@0: Console::__noSuchMethod__() michael@0: { michael@0: // Nothing to do. michael@0: } michael@0: michael@0: static michael@0: nsresult michael@0: StackFrameToStackEntry(nsIStackFrame* aStackFrame, michael@0: ConsoleStackEntry& aStackEntry, michael@0: uint32_t aLanguage) michael@0: { michael@0: MOZ_ASSERT(aStackFrame); michael@0: michael@0: nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t lineNumber; michael@0: rv = aStackFrame->GetLineNumber(&lineNumber); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aStackEntry.mLineNumber = lineNumber; michael@0: michael@0: rv = aStackFrame->GetName(aStackEntry.mFunctionName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aStackEntry.mLanguage = aLanguage; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static michael@0: nsresult michael@0: ReifyStack(nsIStackFrame* aStack, nsTArray& aRefiedStack) michael@0: { michael@0: nsCOMPtr stack(aStack); michael@0: michael@0: while (stack) { michael@0: uint32_t language; michael@0: nsresult rv = stack->GetLanguage(&language); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (language == nsIProgrammingLanguage::JAVASCRIPT || michael@0: language == nsIProgrammingLanguage::JAVASCRIPT2) { michael@0: ConsoleStackEntry& data = *aRefiedStack.AppendElement(); michael@0: rv = StackFrameToStackEntry(stack, data, language); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr caller; michael@0: rv = stack->GetCaller(getter_AddRefs(caller)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: stack.swap(caller); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Queue a call to a console method. See the CALL_DELAY constant. michael@0: void michael@0: Console::Method(JSContext* aCx, MethodName aMethodName, michael@0: const nsAString& aMethodString, michael@0: const Sequence& aData) michael@0: { michael@0: // This RAII class removes the last element of the mQueuedCalls if something michael@0: // goes wrong. michael@0: class RAII { michael@0: public: michael@0: RAII(LinkedList& aList) michael@0: : mList(aList) michael@0: , mUnfinished(true) michael@0: { michael@0: } michael@0: michael@0: ~RAII() michael@0: { michael@0: if (mUnfinished) { michael@0: ConsoleCallData* data = mList.popLast(); michael@0: MOZ_ASSERT(data); michael@0: delete data; michael@0: } michael@0: } michael@0: michael@0: void michael@0: Finished() michael@0: { michael@0: mUnfinished = false; michael@0: } michael@0: michael@0: private: michael@0: LinkedList& mList; michael@0: bool mUnfinished; michael@0: }; michael@0: michael@0: ConsoleCallData* callData = new ConsoleCallData(); michael@0: mQueuedCalls.insertBack(callData); michael@0: michael@0: ClearException ce(aCx); michael@0: michael@0: callData->Initialize(aCx, aMethodName, aMethodString, aData); michael@0: RAII raii(mQueuedCalls); michael@0: michael@0: if (mWindow) { michael@0: nsCOMPtr webNav = do_GetInterface(mWindow); michael@0: if (!webNav) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr loadContext = do_QueryInterface(webNav); michael@0: MOZ_ASSERT(loadContext); michael@0: michael@0: loadContext->GetUsePrivateBrowsing(&callData->mPrivate); michael@0: } michael@0: michael@0: uint32_t maxDepth = ShouldIncludeStackrace(aMethodName) ? michael@0: DEFAULT_MAX_STACKTRACE_DEPTH : 1; michael@0: nsCOMPtr stack = CreateStack(aCx, maxDepth); michael@0: michael@0: if (!stack) { michael@0: return; michael@0: } michael@0: michael@0: // Walk up to the first JS stack frame and save it if we find it. michael@0: do { michael@0: uint32_t language; michael@0: nsresult rv = stack->GetLanguage(&language); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: if (language == nsIProgrammingLanguage::JAVASCRIPT || michael@0: language == nsIProgrammingLanguage::JAVASCRIPT2) { michael@0: callData->mTopStackFrame.construct(); michael@0: nsresult rv = StackFrameToStackEntry(stack, michael@0: callData->mTopStackFrame.ref(), michael@0: language); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr caller; michael@0: rv = stack->GetCaller(getter_AddRefs(caller)); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: stack.swap(caller); michael@0: } while (stack); michael@0: michael@0: if (NS_IsMainThread()) { michael@0: callData->mStack = stack; michael@0: } else { michael@0: // nsIStackFrame is not threadsafe, so we need to snapshot it now, michael@0: // before we post our runnable to the main thread. michael@0: callData->mReifiedStack.construct(); michael@0: nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref()); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Monotonic timer for 'time' and 'timeEnd' michael@0: if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd)) { michael@0: if (mWindow) { michael@0: nsGlobalWindow *win = static_cast(mWindow.get()); michael@0: MOZ_ASSERT(win); michael@0: michael@0: ErrorResult rv; michael@0: nsRefPtr performance = win->GetPerformance(rv); michael@0: if (rv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: callData->mMonotonicTimer = performance->Now(); michael@0: } else { michael@0: WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); michael@0: MOZ_ASSERT(workerPrivate); michael@0: michael@0: TimeDuration duration = michael@0: mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp(); michael@0: michael@0: callData->mMonotonicTimer = duration.ToMilliseconds(); michael@0: } michael@0: } michael@0: michael@0: // The operation is completed. RAII class has to be disabled. michael@0: raii.Finished(); michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: // Here we are in a worker thread. The ConsoleCallData has to been removed michael@0: // from the list and it will be deleted by the ConsoleCallDataRunnable or michael@0: // by the Main-Thread Console object. michael@0: mQueuedCalls.popLast(); michael@0: michael@0: nsRefPtr runnable = michael@0: new ConsoleCallDataRunnable(callData); michael@0: runnable->Dispatch(); michael@0: return; michael@0: } michael@0: michael@0: if (!mTimer) { michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mTimer->InitWithCallback(this, CALL_DELAY, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Console::AppendCallData(ConsoleCallData* aCallData) michael@0: { michael@0: mQueuedCalls.insertBack(aCallData); michael@0: michael@0: if (!mTimer) { michael@0: mTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mTimer->InitWithCallback(this, CALL_DELAY, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: } michael@0: michael@0: // Timer callback used to process each of the queued calls. michael@0: NS_IMETHODIMP michael@0: Console::Notify(nsITimer *timer) michael@0: { michael@0: MOZ_ASSERT(!mQueuedCalls.isEmpty()); michael@0: michael@0: for (uint32_t i = 0; i < MESSAGES_IN_INTERVAL; ++i) { michael@0: ConsoleCallData* data = mQueuedCalls.popFirst(); michael@0: if (!data) { michael@0: break; michael@0: } michael@0: michael@0: ProcessCallData(data); michael@0: delete data; michael@0: } michael@0: michael@0: if (mQueuedCalls.isEmpty() && mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We store information to lazily compute the stack in the reserved slots of michael@0: // LazyStackGetter. The first slot always stores a JS object: it's either the michael@0: // JS wrapper of the nsIStackFrame or the actual reified stack representation. michael@0: // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't michael@0: // reified the stack yet, or an UndefinedValue() otherwise. michael@0: enum { michael@0: SLOT_STACKOBJ, michael@0: SLOT_RAW_STACK michael@0: }; michael@0: michael@0: bool michael@0: LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) michael@0: { michael@0: JS::CallArgs args = CallArgsFromVp(aArgc, aVp); michael@0: JS::Rooted callee(aCx, &args.callee()); michael@0: michael@0: JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK); michael@0: if (v.isUndefined()) { michael@0: // Already reified. michael@0: args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ)); michael@0: return true; michael@0: } michael@0: michael@0: nsIStackFrame* stack = reinterpret_cast(v.toPrivate()); michael@0: nsTArray reifiedStack; michael@0: nsresult rv = ReifyStack(stack, reifiedStack); michael@0: if (NS_FAILED(rv)) { michael@0: Throw(aCx, rv); michael@0: return false; michael@0: } michael@0: michael@0: JS::Rooted stackVal(aCx); michael@0: if (!ToJSValue(aCx, reifiedStack, &stackVal)) { michael@0: return false; michael@0: } michael@0: michael@0: MOZ_ASSERT(stackVal.isObject()); michael@0: michael@0: js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal); michael@0: js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue()); michael@0: michael@0: args.rval().set(stackVal); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: Console::ProcessCallData(ConsoleCallData* aData) michael@0: { michael@0: MOZ_ASSERT(aData); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: ConsoleStackEntry frame; michael@0: if (!aData->mTopStackFrame.empty()) { michael@0: frame = aData->mTopStackFrame.ref(); michael@0: } michael@0: michael@0: AutoSafeJSContext cx; michael@0: ClearException ce(cx); michael@0: RootedDictionary event(cx); michael@0: michael@0: JSAutoCompartment ac(cx, aData->mGlobal); michael@0: michael@0: event.mID.Construct(); michael@0: event.mInnerID.Construct(); michael@0: if (mWindow) { michael@0: event.mID.Value().SetAsUnsignedLong() = mOuterID; michael@0: event.mInnerID.Value().SetAsUnsignedLong() = mInnerID; michael@0: } else { michael@0: // If we are in a JSM, the window doesn't exist. michael@0: event.mID.Value().SetAsString() = NS_LITERAL_STRING("jsm"); michael@0: event.mInnerID.Value().SetAsString() = frame.mFilename; michael@0: } michael@0: michael@0: event.mLevel = aData->mMethodString; michael@0: event.mFilename = frame.mFilename; michael@0: event.mLineNumber = frame.mLineNumber; michael@0: event.mFunctionName = frame.mFunctionName; michael@0: event.mTimeStamp = aData->mTimeStamp; michael@0: event.mPrivate = aData->mPrivate; michael@0: michael@0: switch (aData->mMethodName) { michael@0: case MethodLog: michael@0: case MethodInfo: michael@0: case MethodWarn: michael@0: case MethodError: michael@0: case MethodException: michael@0: case MethodDebug: michael@0: case MethodAssert: michael@0: event.mArguments.Construct(); michael@0: event.mStyles.Construct(); michael@0: ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), michael@0: event.mStyles.Value()); michael@0: break; michael@0: michael@0: default: michael@0: event.mArguments.Construct(); michael@0: ArgumentsToValueList(aData->mArguments, event.mArguments.Value()); michael@0: } michael@0: michael@0: if (aData->mMethodName == MethodGroup || michael@0: aData->mMethodName == MethodGroupCollapsed || michael@0: aData->mMethodName == MethodGroupEnd) { michael@0: ComposeGroupName(cx, aData->mArguments, event.mGroupName); michael@0: } michael@0: michael@0: else if (aData->mMethodName == MethodTime && !aData->mArguments.IsEmpty()) { michael@0: event.mTimer = StartTimer(cx, aData->mArguments[0], aData->mMonotonicTimer); michael@0: } michael@0: michael@0: else if (aData->mMethodName == MethodTimeEnd && !aData->mArguments.IsEmpty()) { michael@0: event.mTimer = StopTimer(cx, aData->mArguments[0], aData->mMonotonicTimer); michael@0: } michael@0: michael@0: else if (aData->mMethodName == MethodCount) { michael@0: event.mCounter = IncreaseCounter(cx, frame, aData->mArguments); michael@0: } michael@0: michael@0: // We want to create a console event object and pass it to our michael@0: // nsIConsoleAPIStorage implementation. We want to define some accessor michael@0: // properties on this object, and those will need to keep an nsIStackFrame michael@0: // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And michael@0: // further, passing untrusted objects to system code is likely to run afoul of michael@0: // Object Xrays. So we want to wrap in a system-principal scope here. But michael@0: // which one? We could cheat and try to get the underlying JSObject* of michael@0: // mStorage, but that's a bit fragile. Instead, we just use the junk scope, michael@0: // with explicit permission from the XPConnect module owner. If you're michael@0: // tempted to do that anywhere else, talk to said module owner first. michael@0: JSAutoCompartment ac2(cx, xpc::GetJunkScope()); michael@0: michael@0: JS::Rooted eventValue(cx); michael@0: if (!event.ToObject(cx, &eventValue)) { michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted eventObj(cx, &eventValue.toObject()); michael@0: MOZ_ASSERT(eventObj); michael@0: michael@0: if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) { michael@0: return; michael@0: } michael@0: michael@0: if (ShouldIncludeStackrace(aData->mMethodName)) { michael@0: // Now define the "stacktrace" property on eventObj. There are two cases michael@0: // here. Either we came from a worker and have a reified stack, or we want michael@0: // to define a getter that will lazily reify the stack. michael@0: if (!aData->mReifiedStack.empty()) { michael@0: JS::Rooted stacktrace(cx); michael@0: if (!ToJSValue(cx, aData->mReifiedStack.ref(), &stacktrace) || michael@0: !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace, michael@0: JSPROP_ENUMERATE)) { michael@0: return; michael@0: } michael@0: } else { michael@0: JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0, michael@0: eventObj, "stacktrace"); michael@0: if (!fun) { michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted funObj(cx, JS_GetFunctionObject(fun)); michael@0: michael@0: // We want to store our stack in the function and have it stay alive. But michael@0: // we also need sane access to the C++ nsIStackFrame. So store both a JS michael@0: // wrapper and the raw pointer: the former will keep the latter alive. michael@0: JS::Rooted stackVal(cx); michael@0: nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack, michael@0: &stackVal); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal); michael@0: js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK, michael@0: JS::PrivateValue(aData->mStack.get())); michael@0: michael@0: if (!JS_DefineProperty(cx, eventObj, "stacktrace", michael@0: JS::UndefinedHandleValue, michael@0: JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER | michael@0: JSPROP_SETTER, michael@0: JS_DATA_TO_FUNC_PTR(JSPropertyOp, funObj.get()), michael@0: nullptr)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!mStorage) { michael@0: mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1"); michael@0: } michael@0: michael@0: if (!mStorage) { michael@0: NS_WARNING("Failed to get the ConsoleAPIStorage service."); michael@0: return; michael@0: } michael@0: michael@0: nsAutoString innerID; michael@0: innerID.AppendInt(mInnerID); michael@0: michael@0: if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) { michael@0: NS_WARNING("Failed to record a console event."); michael@0: } michael@0: michael@0: nsXPConnect* xpc = nsXPConnect::XPConnect(); michael@0: nsCOMPtr wrapper; michael@0: const nsIID& iid = NS_GET_IID(nsISupports); michael@0: michael@0: if (NS_FAILED(xpc->WrapJS(cx, eventObj, iid, getter_AddRefs(wrapper)))) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr obs = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (obs) { michael@0: nsAutoString outerID; michael@0: outerID.AppendInt(mOuterID); michael@0: michael@0: obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Console::ProcessArguments(JSContext* aCx, michael@0: const nsTArray>& aData, michael@0: Sequence& aSequence, michael@0: Sequence& aStyles) michael@0: { michael@0: if (aData.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: if (aData.Length() == 1 || !aData[0].isString()) { michael@0: ArgumentsToValueList(aData, aSequence); michael@0: return; michael@0: } michael@0: michael@0: JS::Rooted format(aCx, aData[0]); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, format)); michael@0: if (!jsString) { michael@0: return; michael@0: } michael@0: michael@0: nsDependentJSString string; michael@0: if (!string.init(aCx, jsString)) { michael@0: return; michael@0: } michael@0: michael@0: nsString::const_iterator start, end; michael@0: string.BeginReading(start); michael@0: string.EndReading(end); michael@0: michael@0: nsString output; michael@0: uint32_t index = 1; michael@0: michael@0: while (start != end) { michael@0: if (*start != '%') { michael@0: output.Append(*start); michael@0: ++start; michael@0: continue; michael@0: } michael@0: michael@0: ++start; michael@0: if (start == end) { michael@0: output.Append('%'); michael@0: break; michael@0: } michael@0: michael@0: if (*start == '%') { michael@0: output.Append(*start); michael@0: ++start; michael@0: continue; michael@0: } michael@0: michael@0: nsAutoString tmp; michael@0: tmp.Append('%'); michael@0: michael@0: int32_t integer = -1; michael@0: int32_t mantissa = -1; michael@0: michael@0: // Let's parse %. for %d and %f michael@0: if (*start >= '0' && *start <= '9') { michael@0: integer = 0; michael@0: michael@0: do { michael@0: integer = integer * 10 + *start - '0'; michael@0: tmp.Append(*start); michael@0: ++start; michael@0: } while (*start >= '0' && *start <= '9' && start != end); michael@0: } michael@0: michael@0: if (start == end) { michael@0: output.Append(tmp); michael@0: break; michael@0: } michael@0: michael@0: if (*start == '.') { michael@0: tmp.Append(*start); michael@0: ++start; michael@0: michael@0: if (start == end) { michael@0: output.Append(tmp); michael@0: break; michael@0: } michael@0: michael@0: // '.' must be followed by a number. michael@0: if (*start < '0' || *start > '9') { michael@0: output.Append(tmp); michael@0: continue; michael@0: } michael@0: michael@0: mantissa = 0; michael@0: michael@0: do { michael@0: mantissa = mantissa * 10 + *start - '0'; michael@0: tmp.Append(*start); michael@0: ++start; michael@0: } while (*start >= '0' && *start <= '9' && start != end); michael@0: michael@0: if (start == end) { michael@0: output.Append(tmp); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: char ch = *start; michael@0: tmp.Append(ch); michael@0: ++start; michael@0: michael@0: switch (ch) { michael@0: case 'o': michael@0: case 'O': michael@0: { michael@0: if (!output.IsEmpty()) { michael@0: JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, michael@0: output.get(), michael@0: output.Length())); michael@0: if (!str) { michael@0: return; michael@0: } michael@0: michael@0: aSequence.AppendElement(JS::StringValue(str)); michael@0: output.Truncate(); michael@0: } michael@0: michael@0: JS::Rooted v(aCx); michael@0: if (index < aData.Length()) { michael@0: v = aData[index++]; michael@0: } michael@0: michael@0: aSequence.AppendElement(v); michael@0: break; michael@0: } michael@0: michael@0: case 'c': michael@0: { michael@0: if (!output.IsEmpty()) { michael@0: JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, michael@0: output.get(), michael@0: output.Length())); michael@0: if (!str) { michael@0: return; michael@0: } michael@0: michael@0: aSequence.AppendElement(JS::StringValue(str)); michael@0: output.Truncate(); michael@0: } michael@0: michael@0: if (index < aData.Length()) { michael@0: JS::Rooted v(aCx, aData[index++]); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, v)); michael@0: if (!jsString) { michael@0: return; michael@0: } michael@0: michael@0: int32_t diff = aSequence.Length() - aStyles.Length(); michael@0: if (diff > 0) { michael@0: for (int32_t i = 0; i < diff; i++) { michael@0: aStyles.AppendElement(JS::NullValue()); michael@0: } michael@0: } michael@0: aStyles.AppendElement(JS::StringValue(jsString)); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case 's': michael@0: if (index < aData.Length()) { michael@0: JS::Rooted value(aCx, aData[index++]); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, value)); michael@0: if (!jsString) { michael@0: return; michael@0: } michael@0: michael@0: nsDependentJSString v; michael@0: if (!v.init(aCx, jsString)) { michael@0: return; michael@0: } michael@0: michael@0: output.Append(v); michael@0: } michael@0: break; michael@0: michael@0: case 'd': michael@0: case 'i': michael@0: if (index < aData.Length()) { michael@0: JS::Rooted value(aCx, aData[index++]); michael@0: michael@0: int32_t v; michael@0: if (!JS::ToInt32(aCx, value, &v)) { michael@0: return; michael@0: } michael@0: michael@0: nsCString format; michael@0: MakeFormatString(format, integer, mantissa, 'd'); michael@0: output.AppendPrintf(format.get(), v); michael@0: } michael@0: break; michael@0: michael@0: case 'f': michael@0: if (index < aData.Length()) { michael@0: JS::Rooted value(aCx, aData[index++]); michael@0: michael@0: double v; michael@0: if (!JS::ToNumber(aCx, value, &v)) { michael@0: return; michael@0: } michael@0: michael@0: nsCString format; michael@0: MakeFormatString(format, integer, mantissa, 'f'); michael@0: output.AppendPrintf(format.get(), v); michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: output.Append(tmp); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!output.IsEmpty()) { michael@0: JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, output.get(), michael@0: output.Length())); michael@0: if (!str) { michael@0: return; michael@0: } michael@0: michael@0: aSequence.AppendElement(JS::StringValue(str)); michael@0: } michael@0: michael@0: // The rest of the array, if unused by the format string. michael@0: for (; index < aData.Length(); ++index) { michael@0: aSequence.AppendElement(aData[index]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Console::MakeFormatString(nsCString& aFormat, int32_t aInteger, michael@0: int32_t aMantissa, char aCh) michael@0: { michael@0: aFormat.Append("%"); michael@0: if (aInteger >= 0) { michael@0: aFormat.AppendInt(aInteger); michael@0: } michael@0: michael@0: if (aMantissa >= 0) { michael@0: aFormat.Append("."); michael@0: aFormat.AppendInt(aMantissa); michael@0: } michael@0: michael@0: aFormat.Append(aCh); michael@0: } michael@0: michael@0: void michael@0: Console::ComposeGroupName(JSContext* aCx, michael@0: const nsTArray>& aData, michael@0: nsAString& aName) michael@0: { michael@0: for (uint32_t i = 0; i < aData.Length(); ++i) { michael@0: if (i != 0) { michael@0: aName.AppendASCII(" "); michael@0: } michael@0: michael@0: JS::Rooted value(aCx, aData[i]); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, value)); michael@0: if (!jsString) { michael@0: return; michael@0: } michael@0: michael@0: nsDependentJSString string; michael@0: if (!string.init(aCx, jsString)) { michael@0: return; michael@0: } michael@0: michael@0: aName.Append(string); michael@0: } michael@0: } michael@0: michael@0: JS::Value michael@0: Console::StartTimer(JSContext* aCx, const JS::Value& aName, michael@0: DOMHighResTimeStamp aTimestamp) michael@0: { michael@0: if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) { michael@0: RootedDictionary error(aCx); michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!error.ToObject(aCx, &value)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: RootedDictionary timer(aCx); michael@0: michael@0: JS::Rooted name(aCx, aName); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, name)); michael@0: if (!jsString) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: nsDependentJSString key; michael@0: if (!key.init(aCx, jsString)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: timer.mName = key; michael@0: michael@0: DOMHighResTimeStamp entry; michael@0: if (!mTimerRegistry.Get(key, &entry)) { michael@0: mTimerRegistry.Put(key, aTimestamp); michael@0: } else { michael@0: aTimestamp = entry; michael@0: } michael@0: michael@0: timer.mStarted = aTimestamp; michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!timer.ToObject(aCx, &value)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: JS::Value michael@0: Console::StopTimer(JSContext* aCx, const JS::Value& aName, michael@0: DOMHighResTimeStamp aTimestamp) michael@0: { michael@0: JS::Rooted name(aCx, aName); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, name)); michael@0: if (!jsString) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: nsDependentJSString key; michael@0: if (!key.init(aCx, jsString)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: DOMHighResTimeStamp entry; michael@0: if (!mTimerRegistry.Get(key, &entry)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: mTimerRegistry.Remove(key); michael@0: michael@0: RootedDictionary timer(aCx); michael@0: timer.mName = key; michael@0: timer.mDuration = aTimestamp - entry; michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!timer.ToObject(aCx, &value)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: void michael@0: Console::ArgumentsToValueList(const nsTArray>& aData, michael@0: Sequence& aSequence) michael@0: { michael@0: for (uint32_t i = 0; i < aData.Length(); ++i) { michael@0: aSequence.AppendElement(aData[i]); michael@0: } michael@0: } michael@0: michael@0: JS::Value michael@0: Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame, michael@0: const nsTArray>& aArguments) michael@0: { michael@0: ClearException ce(aCx); michael@0: michael@0: nsAutoString key; michael@0: nsAutoString label; michael@0: michael@0: if (!aArguments.IsEmpty()) { michael@0: JS::Rooted labelValue(aCx, aArguments[0]); michael@0: JS::Rooted jsString(aCx, JS::ToString(aCx, labelValue)); michael@0: michael@0: nsDependentJSString string; michael@0: if (jsString && string.init(aCx, jsString)) { michael@0: label = string; michael@0: key = string; michael@0: } michael@0: } michael@0: michael@0: if (key.IsEmpty()) { michael@0: key.Append(aFrame.mFilename); michael@0: key.Append(NS_LITERAL_STRING(":")); michael@0: key.AppendInt(aFrame.mLineNumber); michael@0: } michael@0: michael@0: uint32_t count = 0; michael@0: if (!mCounterRegistry.Get(key, &count)) { michael@0: if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) { michael@0: RootedDictionary error(aCx); michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!error.ToObject(aCx, &value)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: } michael@0: michael@0: ++count; michael@0: mCounterRegistry.Put(key, count); michael@0: michael@0: RootedDictionary data(aCx); michael@0: data.mLabel = label; michael@0: data.mCount = count; michael@0: michael@0: JS::Rooted value(aCx); michael@0: if (!data.ToObject(aCx, &value)) { michael@0: return JS::UndefinedValue(); michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: void michael@0: Console::ClearConsoleData() michael@0: { michael@0: while (ConsoleCallData* data = mQueuedCalls.popFirst()) { michael@0: delete data; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Console::ShouldIncludeStackrace(MethodName aMethodName) michael@0: { michael@0: switch (aMethodName) { michael@0: case MethodError: michael@0: case MethodException: michael@0: case MethodAssert: michael@0: case MethodTrace: michael@0: return true; michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla