1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/Console.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1646 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/dom/Console.h" 1.10 +#include "mozilla/dom/ConsoleBinding.h" 1.11 + 1.12 +#include "mozilla/dom/Exceptions.h" 1.13 +#include "mozilla/dom/ToJSValue.h" 1.14 +#include "mozilla/Maybe.h" 1.15 +#include "nsCycleCollectionParticipant.h" 1.16 +#include "nsDocument.h" 1.17 +#include "nsDOMNavigationTiming.h" 1.18 +#include "nsGlobalWindow.h" 1.19 +#include "nsJSUtils.h" 1.20 +#include "nsPerformance.h" 1.21 +#include "WorkerPrivate.h" 1.22 +#include "WorkerRunnable.h" 1.23 +#include "xpcprivate.h" 1.24 +#include "nsContentUtils.h" 1.25 + 1.26 +#include "nsIConsoleAPIStorage.h" 1.27 +#include "nsIDOMWindowUtils.h" 1.28 +#include "nsIInterfaceRequestorUtils.h" 1.29 +#include "nsILoadContext.h" 1.30 +#include "nsIServiceManager.h" 1.31 +#include "nsISupportsPrimitives.h" 1.32 +#include "nsIWebNavigation.h" 1.33 + 1.34 +// The maximum allowed number of concurrent timers per page. 1.35 +#define MAX_PAGE_TIMERS 10000 1.36 + 1.37 +// The maximum allowed number of concurrent counters per page. 1.38 +#define MAX_PAGE_COUNTERS 10000 1.39 + 1.40 +// The maximum stacktrace depth when populating the stacktrace array used for 1.41 +// console.trace(). 1.42 +#define DEFAULT_MAX_STACKTRACE_DEPTH 200 1.43 + 1.44 +// The console API methods are async and their action is executed later. This 1.45 +// delay tells how much later. 1.46 +#define CALL_DELAY 15 // milliseconds 1.47 + 1.48 +// This constant tells how many messages to process in a single timer execution. 1.49 +#define MESSAGES_IN_INTERVAL 1500 1.50 + 1.51 +// This tag is used in the Structured Clone Algorithm to move js values from 1.52 +// worker thread to main thread 1.53 +#define CONSOLE_TAG JS_SCTAG_USER_MIN 1.54 + 1.55 +using namespace mozilla::dom::exceptions; 1.56 +using namespace mozilla::dom::workers; 1.57 + 1.58 +namespace mozilla { 1.59 +namespace dom { 1.60 + 1.61 +/** 1.62 + * Console API in workers uses the Structured Clone Algorithm to move any value 1.63 + * from the worker thread to the main-thread. Some object cannot be moved and, 1.64 + * in these cases, we convert them to strings. 1.65 + * It's not the best, but at least we are able to show something. 1.66 + */ 1.67 + 1.68 +// This method is called by the Structured Clone Algorithm when some data has 1.69 +// to be read. 1.70 +static JSObject* 1.71 +ConsoleStructuredCloneCallbacksRead(JSContext* aCx, 1.72 + JSStructuredCloneReader* /* unused */, 1.73 + uint32_t aTag, uint32_t aData, 1.74 + void* aClosure) 1.75 +{ 1.76 + AssertIsOnMainThread(); 1.77 + 1.78 + if (aTag != CONSOLE_TAG) { 1.79 + return nullptr; 1.80 + } 1.81 + 1.82 + nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure); 1.83 + MOZ_ASSERT(strings->Length() > aData); 1.84 + 1.85 + JS::Rooted<JS::Value> value(aCx); 1.86 + if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) { 1.87 + return nullptr; 1.88 + } 1.89 + 1.90 + JS::Rooted<JSObject*> obj(aCx); 1.91 + if (!JS_ValueToObject(aCx, value, &obj)) { 1.92 + return nullptr; 1.93 + } 1.94 + 1.95 + return obj; 1.96 +} 1.97 + 1.98 +// This method is called by the Structured Clone Algorithm when some data has 1.99 +// to be written. 1.100 +static bool 1.101 +ConsoleStructuredCloneCallbacksWrite(JSContext* aCx, 1.102 + JSStructuredCloneWriter* aWriter, 1.103 + JS::Handle<JSObject*> aObj, 1.104 + void* aClosure) 1.105 +{ 1.106 + JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj)); 1.107 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 1.108 + if (!jsString) { 1.109 + return false; 1.110 + } 1.111 + 1.112 + nsDependentJSString string; 1.113 + if (!string.init(aCx, jsString)) { 1.114 + return false; 1.115 + } 1.116 + 1.117 + nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure); 1.118 + 1.119 + if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) { 1.120 + return false; 1.121 + } 1.122 + 1.123 + strings->AppendElement(string); 1.124 + 1.125 + return true; 1.126 +} 1.127 + 1.128 +static void 1.129 +ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */, 1.130 + uint32_t /* aErrorId */) 1.131 +{ 1.132 + NS_WARNING("Failed to clone data for the Console API in workers."); 1.133 +} 1.134 + 1.135 +JSStructuredCloneCallbacks gConsoleCallbacks = { 1.136 + ConsoleStructuredCloneCallbacksRead, 1.137 + ConsoleStructuredCloneCallbacksWrite, 1.138 + ConsoleStructuredCloneCallbacksError 1.139 +}; 1.140 + 1.141 +class ConsoleCallData MOZ_FINAL : public LinkedListElement<ConsoleCallData> 1.142 +{ 1.143 +public: 1.144 + ConsoleCallData() 1.145 + : mMethodName(Console::MethodLog) 1.146 + , mPrivate(false) 1.147 + , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC) 1.148 + , mMonotonicTimer(0) 1.149 + { 1.150 + MOZ_COUNT_CTOR(ConsoleCallData); 1.151 + } 1.152 + 1.153 + ~ConsoleCallData() 1.154 + { 1.155 + MOZ_COUNT_DTOR(ConsoleCallData); 1.156 + } 1.157 + 1.158 + void 1.159 + Initialize(JSContext* aCx, Console::MethodName aName, 1.160 + const nsAString& aString, const Sequence<JS::Value>& aArguments) 1.161 + { 1.162 + mGlobal = JS::CurrentGlobalOrNull(aCx); 1.163 + mMethodName = aName; 1.164 + mMethodString = aString; 1.165 + 1.166 + for (uint32_t i = 0; i < aArguments.Length(); ++i) { 1.167 + mArguments.AppendElement(aArguments[i]); 1.168 + } 1.169 + } 1.170 + 1.171 + JS::Heap<JSObject*> mGlobal; 1.172 + 1.173 + Console::MethodName mMethodName; 1.174 + bool mPrivate; 1.175 + int64_t mTimeStamp; 1.176 + DOMHighResTimeStamp mMonotonicTimer; 1.177 + 1.178 + nsString mMethodString; 1.179 + nsTArray<JS::Heap<JS::Value>> mArguments; 1.180 + 1.181 + // Stack management is complicated, because we want to do it as 1.182 + // lazily as possible. Therefore, we have the following behavior: 1.183 + // 1) mTopStackFrame is initialized whenever we have any JS on the stack 1.184 + // 2) mReifiedStack is initialized if we're created in a worker. 1.185 + // 3) mStack is set (possibly to null if there is no JS on the stack) if 1.186 + // we're created on main thread. 1.187 + Maybe<ConsoleStackEntry> mTopStackFrame; 1.188 + Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack; 1.189 + nsCOMPtr<nsIStackFrame> mStack; 1.190 +}; 1.191 + 1.192 +// This class is used to clear any exception at the end of this method. 1.193 +class ClearException 1.194 +{ 1.195 +public: 1.196 + ClearException(JSContext* aCx) 1.197 + : mCx(aCx) 1.198 + { 1.199 + } 1.200 + 1.201 + ~ClearException() 1.202 + { 1.203 + JS_ClearPendingException(mCx); 1.204 + } 1.205 + 1.206 +private: 1.207 + JSContext* mCx; 1.208 +}; 1.209 + 1.210 +class ConsoleRunnable : public nsRunnable 1.211 +{ 1.212 +public: 1.213 + ConsoleRunnable() 1.214 + : mWorkerPrivate(GetCurrentThreadWorkerPrivate()) 1.215 + { 1.216 + MOZ_ASSERT(mWorkerPrivate); 1.217 + } 1.218 + 1.219 + virtual 1.220 + ~ConsoleRunnable() 1.221 + { 1.222 + } 1.223 + 1.224 + bool 1.225 + Dispatch() 1.226 + { 1.227 + mWorkerPrivate->AssertIsOnWorkerThread(); 1.228 + 1.229 + JSContext* cx = mWorkerPrivate->GetJSContext(); 1.230 + 1.231 + if (!PreDispatch(cx)) { 1.232 + return false; 1.233 + } 1.234 + 1.235 + AutoSyncLoopHolder syncLoop(mWorkerPrivate); 1.236 + mSyncLoopTarget = syncLoop.EventTarget(); 1.237 + 1.238 + if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) { 1.239 + JS_ReportError(cx, 1.240 + "Failed to dispatch to main thread for the Console API!"); 1.241 + return false; 1.242 + } 1.243 + 1.244 + return syncLoop.Run(); 1.245 + } 1.246 + 1.247 +private: 1.248 + NS_IMETHOD Run() 1.249 + { 1.250 + AssertIsOnMainThread(); 1.251 + 1.252 + RunConsole(); 1.253 + 1.254 + nsRefPtr<MainThreadStopSyncLoopRunnable> response = 1.255 + new MainThreadStopSyncLoopRunnable(mWorkerPrivate, 1.256 + mSyncLoopTarget.forget(), 1.257 + true); 1.258 + if (!response->Dispatch(nullptr)) { 1.259 + NS_WARNING("Failed to dispatch response!"); 1.260 + } 1.261 + 1.262 + return NS_OK; 1.263 + } 1.264 + 1.265 +protected: 1.266 + virtual bool 1.267 + PreDispatch(JSContext* aCx) = 0; 1.268 + 1.269 + virtual void 1.270 + RunConsole() = 0; 1.271 + 1.272 + WorkerPrivate* mWorkerPrivate; 1.273 + 1.274 +private: 1.275 + nsCOMPtr<nsIEventTarget> mSyncLoopTarget; 1.276 +}; 1.277 + 1.278 +// This runnable appends a CallData object into the Console queue running on 1.279 +// the main-thread. 1.280 +class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable 1.281 +{ 1.282 +public: 1.283 + ConsoleCallDataRunnable(ConsoleCallData* aCallData) 1.284 + : mCallData(aCallData) 1.285 + { 1.286 + } 1.287 + 1.288 +private: 1.289 + bool 1.290 + PreDispatch(JSContext* aCx) MOZ_OVERRIDE 1.291 + { 1.292 + ClearException ce(aCx); 1.293 + JSAutoCompartment ac(aCx, mCallData->mGlobal); 1.294 + 1.295 + JS::Rooted<JSObject*> arguments(aCx, 1.296 + JS_NewArrayObject(aCx, mCallData->mArguments.Length())); 1.297 + if (!arguments) { 1.298 + return false; 1.299 + } 1.300 + 1.301 + for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) { 1.302 + if (!JS_DefineElement(aCx, arguments, i, mCallData->mArguments[i], 1.303 + nullptr, nullptr, JSPROP_ENUMERATE)) { 1.304 + return false; 1.305 + } 1.306 + } 1.307 + 1.308 + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments)); 1.309 + 1.310 + if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) { 1.311 + return false; 1.312 + } 1.313 + 1.314 + mCallData->mArguments.Clear(); 1.315 + mCallData->mGlobal = nullptr; 1.316 + return true; 1.317 + } 1.318 + 1.319 + void 1.320 + RunConsole() MOZ_OVERRIDE 1.321 + { 1.322 + // Walk up to our containing page 1.323 + WorkerPrivate* wp = mWorkerPrivate; 1.324 + while (wp->GetParent()) { 1.325 + wp = wp->GetParent(); 1.326 + } 1.327 + 1.328 + AutoPushJSContext cx(wp->ParentJSContext()); 1.329 + ClearException ce(cx); 1.330 + 1.331 + nsPIDOMWindow* window = wp->GetWindow(); 1.332 + NS_ENSURE_TRUE_VOID(window); 1.333 + 1.334 + nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window); 1.335 + NS_ENSURE_TRUE_VOID(win); 1.336 + 1.337 + ErrorResult error; 1.338 + nsRefPtr<Console> console = win->GetConsole(error); 1.339 + if (error.Failed()) { 1.340 + NS_WARNING("Failed to get console from the window."); 1.341 + return; 1.342 + } 1.343 + 1.344 + JS::Rooted<JS::Value> argumentsValue(cx); 1.345 + if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) { 1.346 + return; 1.347 + } 1.348 + 1.349 + MOZ_ASSERT(argumentsValue.isObject()); 1.350 + JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject()); 1.351 + MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj)); 1.352 + 1.353 + uint32_t length; 1.354 + if (!JS_GetArrayLength(cx, argumentsObj, &length)) { 1.355 + return; 1.356 + } 1.357 + 1.358 + for (uint32_t i = 0; i < length; ++i) { 1.359 + JS::Rooted<JS::Value> value(cx); 1.360 + 1.361 + if (!JS_GetElement(cx, argumentsObj, i, &value)) { 1.362 + return; 1.363 + } 1.364 + 1.365 + mCallData->mArguments.AppendElement(value); 1.366 + } 1.367 + 1.368 + MOZ_ASSERT(mCallData->mArguments.Length() == length); 1.369 + 1.370 + mCallData->mGlobal = JS::CurrentGlobalOrNull(cx); 1.371 + console->AppendCallData(mCallData.forget()); 1.372 + } 1.373 + 1.374 +private: 1.375 + nsAutoPtr<ConsoleCallData> mCallData; 1.376 + 1.377 + JSAutoStructuredCloneBuffer mArguments; 1.378 + nsTArray<nsString> mStrings; 1.379 +}; 1.380 + 1.381 +// This runnable calls ProfileMethod() on the console on the main-thread. 1.382 +class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable 1.383 +{ 1.384 +public: 1.385 + ConsoleProfileRunnable(const nsAString& aAction, 1.386 + const Sequence<JS::Value>& aArguments) 1.387 + : mAction(aAction) 1.388 + , mArguments(aArguments) 1.389 + { 1.390 + } 1.391 + 1.392 +private: 1.393 + bool 1.394 + PreDispatch(JSContext* aCx) MOZ_OVERRIDE 1.395 + { 1.396 + ClearException ce(aCx); 1.397 + 1.398 + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); 1.399 + if (!global) { 1.400 + return false; 1.401 + } 1.402 + 1.403 + JSAutoCompartment ac(aCx, global); 1.404 + 1.405 + JS::Rooted<JSObject*> arguments(aCx, 1.406 + JS_NewArrayObject(aCx, mArguments.Length())); 1.407 + if (!arguments) { 1.408 + return false; 1.409 + } 1.410 + 1.411 + for (uint32_t i = 0; i < mArguments.Length(); ++i) { 1.412 + if (!JS_DefineElement(aCx, arguments, i, mArguments[i], nullptr, nullptr, 1.413 + JSPROP_ENUMERATE)) { 1.414 + return false; 1.415 + } 1.416 + } 1.417 + 1.418 + JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments)); 1.419 + 1.420 + if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) { 1.421 + return false; 1.422 + } 1.423 + 1.424 + return true; 1.425 + } 1.426 + 1.427 + void 1.428 + RunConsole() MOZ_OVERRIDE 1.429 + { 1.430 + // Walk up to our containing page 1.431 + WorkerPrivate* wp = mWorkerPrivate; 1.432 + while (wp->GetParent()) { 1.433 + wp = wp->GetParent(); 1.434 + } 1.435 + 1.436 + AutoPushJSContext cx(wp->ParentJSContext()); 1.437 + ClearException ce(cx); 1.438 + 1.439 + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 1.440 + NS_ENSURE_TRUE_VOID(global); 1.441 + JSAutoCompartment ac(cx, global); 1.442 + 1.443 + nsPIDOMWindow* window = wp->GetWindow(); 1.444 + NS_ENSURE_TRUE_VOID(window); 1.445 + 1.446 + nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window); 1.447 + NS_ENSURE_TRUE_VOID(win); 1.448 + 1.449 + ErrorResult error; 1.450 + nsRefPtr<Console> console = win->GetConsole(error); 1.451 + if (error.Failed()) { 1.452 + NS_WARNING("Failed to get console from the window."); 1.453 + return; 1.454 + } 1.455 + 1.456 + JS::Rooted<JS::Value> argumentsValue(cx); 1.457 + if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) { 1.458 + return; 1.459 + } 1.460 + 1.461 + MOZ_ASSERT(argumentsValue.isObject()); 1.462 + JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject()); 1.463 + MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj)); 1.464 + 1.465 + uint32_t length; 1.466 + if (!JS_GetArrayLength(cx, argumentsObj, &length)) { 1.467 + return; 1.468 + } 1.469 + 1.470 + Sequence<JS::Value> arguments; 1.471 + 1.472 + for (uint32_t i = 0; i < length; ++i) { 1.473 + JS::Rooted<JS::Value> value(cx); 1.474 + 1.475 + if (!JS_GetElement(cx, argumentsObj, i, &value)) { 1.476 + return; 1.477 + } 1.478 + 1.479 + arguments.AppendElement(value); 1.480 + } 1.481 + 1.482 + console->ProfileMethod(cx, mAction, arguments); 1.483 + } 1.484 + 1.485 +private: 1.486 + nsString mAction; 1.487 + Sequence<JS::Value> mArguments; 1.488 + 1.489 + JSAutoStructuredCloneBuffer mBuffer; 1.490 + nsTArray<nsString> mStrings; 1.491 +}; 1.492 + 1.493 +NS_IMPL_CYCLE_COLLECTION_CLASS(Console) 1.494 + 1.495 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console) 1.496 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) 1.497 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer) 1.498 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage) 1.499 + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 1.500 + 1.501 + tmp->ClearConsoleData(); 1.502 + 1.503 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.504 + 1.505 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console) 1.506 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) 1.507 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer) 1.508 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage) 1.509 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.510 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.511 + 1.512 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console) 1.513 + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER 1.514 + 1.515 + for (ConsoleCallData* data = tmp->mQueuedCalls.getFirst(); data != nullptr; 1.516 + data = data->getNext()) { 1.517 + if (data->mGlobal) { 1.518 + aCallbacks.Trace(&data->mGlobal, "data->mGlobal", aClosure); 1.519 + } 1.520 + 1.521 + for (uint32_t i = 0; i < data->mArguments.Length(); ++i) { 1.522 + aCallbacks.Trace(&data->mArguments[i], "data->mArguments[i]", aClosure); 1.523 + } 1.524 + } 1.525 + 1.526 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.527 + 1.528 +NS_IMPL_CYCLE_COLLECTING_ADDREF(Console) 1.529 +NS_IMPL_CYCLE_COLLECTING_RELEASE(Console) 1.530 + 1.531 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console) 1.532 + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 1.533 + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 1.534 + NS_INTERFACE_MAP_ENTRY(nsIObserver) 1.535 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) 1.536 +NS_INTERFACE_MAP_END 1.537 + 1.538 +Console::Console(nsPIDOMWindow* aWindow) 1.539 + : mWindow(aWindow) 1.540 + , mOuterID(0) 1.541 + , mInnerID(0) 1.542 +{ 1.543 + if (mWindow) { 1.544 + MOZ_ASSERT(mWindow->IsInnerWindow()); 1.545 + mInnerID = mWindow->WindowID(); 1.546 + 1.547 + nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow(); 1.548 + MOZ_ASSERT(outerWindow); 1.549 + mOuterID = outerWindow->WindowID(); 1.550 + } 1.551 + 1.552 + if (NS_IsMainThread()) { 1.553 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.554 + if (obs) { 1.555 + obs->AddObserver(this, "inner-window-destroyed", false); 1.556 + } 1.557 + } 1.558 + 1.559 + SetIsDOMBinding(); 1.560 + mozilla::HoldJSObjects(this); 1.561 +} 1.562 + 1.563 +Console::~Console() 1.564 +{ 1.565 + mozilla::DropJSObjects(this); 1.566 +} 1.567 + 1.568 +NS_IMETHODIMP 1.569 +Console::Observe(nsISupports* aSubject, const char* aTopic, 1.570 + const char16_t* aData) 1.571 +{ 1.572 + MOZ_ASSERT(NS_IsMainThread()); 1.573 + 1.574 + if (strcmp(aTopic, "inner-window-destroyed")) { 1.575 + return NS_OK; 1.576 + } 1.577 + 1.578 + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); 1.579 + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); 1.580 + 1.581 + uint64_t innerID; 1.582 + nsresult rv = wrapper->GetData(&innerID); 1.583 + NS_ENSURE_SUCCESS(rv, rv); 1.584 + 1.585 + if (innerID == mInnerID) { 1.586 + nsCOMPtr<nsIObserverService> obs = 1.587 + do_GetService("@mozilla.org/observer-service;1"); 1.588 + if (obs) { 1.589 + obs->RemoveObserver(this, "inner-window-destroyed"); 1.590 + } 1.591 + 1.592 + ClearConsoleData(); 1.593 + mTimerRegistry.Clear(); 1.594 + 1.595 + if (mTimer) { 1.596 + mTimer->Cancel(); 1.597 + mTimer = nullptr; 1.598 + } 1.599 + } 1.600 + 1.601 + return NS_OK; 1.602 +} 1.603 + 1.604 +JSObject* 1.605 +Console::WrapObject(JSContext* aCx) 1.606 +{ 1.607 + return ConsoleBinding::Wrap(aCx, this); 1.608 +} 1.609 + 1.610 +#define METHOD(name, string) \ 1.611 + void \ 1.612 + Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \ 1.613 + { \ 1.614 + Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \ 1.615 + } 1.616 + 1.617 +METHOD(Log, "log") 1.618 +METHOD(Info, "info") 1.619 +METHOD(Warn, "warn") 1.620 +METHOD(Error, "error") 1.621 +METHOD(Exception, "exception") 1.622 +METHOD(Debug, "debug") 1.623 + 1.624 +void 1.625 +Console::Trace(JSContext* aCx) 1.626 +{ 1.627 + const Sequence<JS::Value> data; 1.628 + Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data); 1.629 +} 1.630 + 1.631 +// Displays an interactive listing of all the properties of an object. 1.632 +METHOD(Dir, "dir"); 1.633 + 1.634 +METHOD(Group, "group") 1.635 +METHOD(GroupCollapsed, "groupCollapsed") 1.636 +METHOD(GroupEnd, "groupEnd") 1.637 + 1.638 +void 1.639 +Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime) 1.640 +{ 1.641 + Sequence<JS::Value> data; 1.642 + SequenceRooter<JS::Value> rooter(aCx, &data); 1.643 + 1.644 + if (!aTime.isUndefined()) { 1.645 + data.AppendElement(aTime); 1.646 + } 1.647 + 1.648 + Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); 1.649 +} 1.650 + 1.651 +void 1.652 +Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime) 1.653 +{ 1.654 + Sequence<JS::Value> data; 1.655 + SequenceRooter<JS::Value> rooter(aCx, &data); 1.656 + 1.657 + if (!aTime.isUndefined()) { 1.658 + data.AppendElement(aTime); 1.659 + } 1.660 + 1.661 + Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); 1.662 +} 1.663 + 1.664 +void 1.665 +Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData) 1.666 +{ 1.667 + ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData); 1.668 +} 1.669 + 1.670 +void 1.671 +Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData) 1.672 +{ 1.673 + ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData); 1.674 +} 1.675 + 1.676 +void 1.677 +Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, 1.678 + const Sequence<JS::Value>& aData) 1.679 +{ 1.680 + if (!NS_IsMainThread()) { 1.681 + // Here we are in a worker thread. 1.682 + nsRefPtr<ConsoleProfileRunnable> runnable = 1.683 + new ConsoleProfileRunnable(aAction, aData); 1.684 + runnable->Dispatch(); 1.685 + return; 1.686 + } 1.687 + 1.688 + ClearException ce(aCx); 1.689 + 1.690 + RootedDictionary<ConsoleProfileEvent> event(aCx); 1.691 + event.mAction = aAction; 1.692 + 1.693 + event.mArguments.Construct(); 1.694 + Sequence<JS::Value>& sequence = event.mArguments.Value(); 1.695 + 1.696 + for (uint32_t i = 0; i < aData.Length(); ++i) { 1.697 + sequence.AppendElement(aData[i]); 1.698 + } 1.699 + 1.700 + JS::Rooted<JS::Value> eventValue(aCx); 1.701 + if (!event.ToObject(aCx, &eventValue)) { 1.702 + return; 1.703 + } 1.704 + 1.705 + JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject()); 1.706 + MOZ_ASSERT(eventObj); 1.707 + 1.708 + if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, 1.709 + JSPROP_ENUMERATE)) { 1.710 + return; 1.711 + } 1.712 + 1.713 + nsXPConnect* xpc = nsXPConnect::XPConnect(); 1.714 + nsCOMPtr<nsISupports> wrapper; 1.715 + const nsIID& iid = NS_GET_IID(nsISupports); 1.716 + 1.717 + if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) { 1.718 + return; 1.719 + } 1.720 + 1.721 + nsCOMPtr<nsIObserverService> obs = 1.722 + do_GetService("@mozilla.org/observer-service;1"); 1.723 + if (obs) { 1.724 + obs->NotifyObservers(wrapper, "console-api-profiler", nullptr); 1.725 + } 1.726 +} 1.727 + 1.728 +void 1.729 +Console::Assert(JSContext* aCx, bool aCondition, 1.730 + const Sequence<JS::Value>& aData) 1.731 +{ 1.732 + if (!aCondition) { 1.733 + Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData); 1.734 + } 1.735 +} 1.736 + 1.737 +METHOD(Count, "count") 1.738 + 1.739 +void 1.740 +Console::__noSuchMethod__() 1.741 +{ 1.742 + // Nothing to do. 1.743 +} 1.744 + 1.745 +static 1.746 +nsresult 1.747 +StackFrameToStackEntry(nsIStackFrame* aStackFrame, 1.748 + ConsoleStackEntry& aStackEntry, 1.749 + uint32_t aLanguage) 1.750 +{ 1.751 + MOZ_ASSERT(aStackFrame); 1.752 + 1.753 + nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename); 1.754 + NS_ENSURE_SUCCESS(rv, rv); 1.755 + 1.756 + int32_t lineNumber; 1.757 + rv = aStackFrame->GetLineNumber(&lineNumber); 1.758 + NS_ENSURE_SUCCESS(rv, rv); 1.759 + 1.760 + aStackEntry.mLineNumber = lineNumber; 1.761 + 1.762 + rv = aStackFrame->GetName(aStackEntry.mFunctionName); 1.763 + NS_ENSURE_SUCCESS(rv, rv); 1.764 + 1.765 + aStackEntry.mLanguage = aLanguage; 1.766 + return NS_OK; 1.767 +} 1.768 + 1.769 +static 1.770 +nsresult 1.771 +ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack) 1.772 +{ 1.773 + nsCOMPtr<nsIStackFrame> stack(aStack); 1.774 + 1.775 + while (stack) { 1.776 + uint32_t language; 1.777 + nsresult rv = stack->GetLanguage(&language); 1.778 + NS_ENSURE_SUCCESS(rv, rv); 1.779 + 1.780 + if (language == nsIProgrammingLanguage::JAVASCRIPT || 1.781 + language == nsIProgrammingLanguage::JAVASCRIPT2) { 1.782 + ConsoleStackEntry& data = *aRefiedStack.AppendElement(); 1.783 + rv = StackFrameToStackEntry(stack, data, language); 1.784 + NS_ENSURE_SUCCESS(rv, rv); 1.785 + } 1.786 + 1.787 + nsCOMPtr<nsIStackFrame> caller; 1.788 + rv = stack->GetCaller(getter_AddRefs(caller)); 1.789 + NS_ENSURE_SUCCESS(rv, rv); 1.790 + 1.791 + stack.swap(caller); 1.792 + } 1.793 + 1.794 + return NS_OK; 1.795 +} 1.796 + 1.797 +// Queue a call to a console method. See the CALL_DELAY constant. 1.798 +void 1.799 +Console::Method(JSContext* aCx, MethodName aMethodName, 1.800 + const nsAString& aMethodString, 1.801 + const Sequence<JS::Value>& aData) 1.802 +{ 1.803 + // This RAII class removes the last element of the mQueuedCalls if something 1.804 + // goes wrong. 1.805 + class RAII { 1.806 + public: 1.807 + RAII(LinkedList<ConsoleCallData>& aList) 1.808 + : mList(aList) 1.809 + , mUnfinished(true) 1.810 + { 1.811 + } 1.812 + 1.813 + ~RAII() 1.814 + { 1.815 + if (mUnfinished) { 1.816 + ConsoleCallData* data = mList.popLast(); 1.817 + MOZ_ASSERT(data); 1.818 + delete data; 1.819 + } 1.820 + } 1.821 + 1.822 + void 1.823 + Finished() 1.824 + { 1.825 + mUnfinished = false; 1.826 + } 1.827 + 1.828 + private: 1.829 + LinkedList<ConsoleCallData>& mList; 1.830 + bool mUnfinished; 1.831 + }; 1.832 + 1.833 + ConsoleCallData* callData = new ConsoleCallData(); 1.834 + mQueuedCalls.insertBack(callData); 1.835 + 1.836 + ClearException ce(aCx); 1.837 + 1.838 + callData->Initialize(aCx, aMethodName, aMethodString, aData); 1.839 + RAII raii(mQueuedCalls); 1.840 + 1.841 + if (mWindow) { 1.842 + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow); 1.843 + if (!webNav) { 1.844 + return; 1.845 + } 1.846 + 1.847 + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav); 1.848 + MOZ_ASSERT(loadContext); 1.849 + 1.850 + loadContext->GetUsePrivateBrowsing(&callData->mPrivate); 1.851 + } 1.852 + 1.853 + uint32_t maxDepth = ShouldIncludeStackrace(aMethodName) ? 1.854 + DEFAULT_MAX_STACKTRACE_DEPTH : 1; 1.855 + nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth); 1.856 + 1.857 + if (!stack) { 1.858 + return; 1.859 + } 1.860 + 1.861 + // Walk up to the first JS stack frame and save it if we find it. 1.862 + do { 1.863 + uint32_t language; 1.864 + nsresult rv = stack->GetLanguage(&language); 1.865 + if (NS_FAILED(rv)) { 1.866 + return; 1.867 + } 1.868 + 1.869 + if (language == nsIProgrammingLanguage::JAVASCRIPT || 1.870 + language == nsIProgrammingLanguage::JAVASCRIPT2) { 1.871 + callData->mTopStackFrame.construct(); 1.872 + nsresult rv = StackFrameToStackEntry(stack, 1.873 + callData->mTopStackFrame.ref(), 1.874 + language); 1.875 + if (NS_FAILED(rv)) { 1.876 + return; 1.877 + } 1.878 + 1.879 + break; 1.880 + } 1.881 + 1.882 + nsCOMPtr<nsIStackFrame> caller; 1.883 + rv = stack->GetCaller(getter_AddRefs(caller)); 1.884 + if (NS_FAILED(rv)) { 1.885 + return; 1.886 + } 1.887 + 1.888 + stack.swap(caller); 1.889 + } while (stack); 1.890 + 1.891 + if (NS_IsMainThread()) { 1.892 + callData->mStack = stack; 1.893 + } else { 1.894 + // nsIStackFrame is not threadsafe, so we need to snapshot it now, 1.895 + // before we post our runnable to the main thread. 1.896 + callData->mReifiedStack.construct(); 1.897 + nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref()); 1.898 + if (NS_WARN_IF(NS_FAILED(rv))) { 1.899 + return; 1.900 + } 1.901 + } 1.902 + 1.903 + // Monotonic timer for 'time' and 'timeEnd' 1.904 + if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd)) { 1.905 + if (mWindow) { 1.906 + nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get()); 1.907 + MOZ_ASSERT(win); 1.908 + 1.909 + ErrorResult rv; 1.910 + nsRefPtr<nsPerformance> performance = win->GetPerformance(rv); 1.911 + if (rv.Failed()) { 1.912 + return; 1.913 + } 1.914 + 1.915 + callData->mMonotonicTimer = performance->Now(); 1.916 + } else { 1.917 + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 1.918 + MOZ_ASSERT(workerPrivate); 1.919 + 1.920 + TimeDuration duration = 1.921 + mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp(); 1.922 + 1.923 + callData->mMonotonicTimer = duration.ToMilliseconds(); 1.924 + } 1.925 + } 1.926 + 1.927 + // The operation is completed. RAII class has to be disabled. 1.928 + raii.Finished(); 1.929 + 1.930 + if (!NS_IsMainThread()) { 1.931 + // Here we are in a worker thread. The ConsoleCallData has to been removed 1.932 + // from the list and it will be deleted by the ConsoleCallDataRunnable or 1.933 + // by the Main-Thread Console object. 1.934 + mQueuedCalls.popLast(); 1.935 + 1.936 + nsRefPtr<ConsoleCallDataRunnable> runnable = 1.937 + new ConsoleCallDataRunnable(callData); 1.938 + runnable->Dispatch(); 1.939 + return; 1.940 + } 1.941 + 1.942 + if (!mTimer) { 1.943 + mTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.944 + mTimer->InitWithCallback(this, CALL_DELAY, 1.945 + nsITimer::TYPE_REPEATING_SLACK); 1.946 + } 1.947 +} 1.948 + 1.949 +void 1.950 +Console::AppendCallData(ConsoleCallData* aCallData) 1.951 +{ 1.952 + mQueuedCalls.insertBack(aCallData); 1.953 + 1.954 + if (!mTimer) { 1.955 + mTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.956 + mTimer->InitWithCallback(this, CALL_DELAY, 1.957 + nsITimer::TYPE_REPEATING_SLACK); 1.958 + } 1.959 +} 1.960 + 1.961 +// Timer callback used to process each of the queued calls. 1.962 +NS_IMETHODIMP 1.963 +Console::Notify(nsITimer *timer) 1.964 +{ 1.965 + MOZ_ASSERT(!mQueuedCalls.isEmpty()); 1.966 + 1.967 + for (uint32_t i = 0; i < MESSAGES_IN_INTERVAL; ++i) { 1.968 + ConsoleCallData* data = mQueuedCalls.popFirst(); 1.969 + if (!data) { 1.970 + break; 1.971 + } 1.972 + 1.973 + ProcessCallData(data); 1.974 + delete data; 1.975 + } 1.976 + 1.977 + if (mQueuedCalls.isEmpty() && mTimer) { 1.978 + mTimer->Cancel(); 1.979 + mTimer = nullptr; 1.980 + } 1.981 + 1.982 + return NS_OK; 1.983 +} 1.984 + 1.985 +// We store information to lazily compute the stack in the reserved slots of 1.986 +// LazyStackGetter. The first slot always stores a JS object: it's either the 1.987 +// JS wrapper of the nsIStackFrame or the actual reified stack representation. 1.988 +// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't 1.989 +// reified the stack yet, or an UndefinedValue() otherwise. 1.990 +enum { 1.991 + SLOT_STACKOBJ, 1.992 + SLOT_RAW_STACK 1.993 +}; 1.994 + 1.995 +bool 1.996 +LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) 1.997 +{ 1.998 + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); 1.999 + JS::Rooted<JSObject*> callee(aCx, &args.callee()); 1.1000 + 1.1001 + JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK); 1.1002 + if (v.isUndefined()) { 1.1003 + // Already reified. 1.1004 + args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ)); 1.1005 + return true; 1.1006 + } 1.1007 + 1.1008 + nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate()); 1.1009 + nsTArray<ConsoleStackEntry> reifiedStack; 1.1010 + nsresult rv = ReifyStack(stack, reifiedStack); 1.1011 + if (NS_FAILED(rv)) { 1.1012 + Throw(aCx, rv); 1.1013 + return false; 1.1014 + } 1.1015 + 1.1016 + JS::Rooted<JS::Value> stackVal(aCx); 1.1017 + if (!ToJSValue(aCx, reifiedStack, &stackVal)) { 1.1018 + return false; 1.1019 + } 1.1020 + 1.1021 + MOZ_ASSERT(stackVal.isObject()); 1.1022 + 1.1023 + js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal); 1.1024 + js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue()); 1.1025 + 1.1026 + args.rval().set(stackVal); 1.1027 + return true; 1.1028 +} 1.1029 + 1.1030 +void 1.1031 +Console::ProcessCallData(ConsoleCallData* aData) 1.1032 +{ 1.1033 + MOZ_ASSERT(aData); 1.1034 + MOZ_ASSERT(NS_IsMainThread()); 1.1035 + 1.1036 + ConsoleStackEntry frame; 1.1037 + if (!aData->mTopStackFrame.empty()) { 1.1038 + frame = aData->mTopStackFrame.ref(); 1.1039 + } 1.1040 + 1.1041 + AutoSafeJSContext cx; 1.1042 + ClearException ce(cx); 1.1043 + RootedDictionary<ConsoleEvent> event(cx); 1.1044 + 1.1045 + JSAutoCompartment ac(cx, aData->mGlobal); 1.1046 + 1.1047 + event.mID.Construct(); 1.1048 + event.mInnerID.Construct(); 1.1049 + if (mWindow) { 1.1050 + event.mID.Value().SetAsUnsignedLong() = mOuterID; 1.1051 + event.mInnerID.Value().SetAsUnsignedLong() = mInnerID; 1.1052 + } else { 1.1053 + // If we are in a JSM, the window doesn't exist. 1.1054 + event.mID.Value().SetAsString() = NS_LITERAL_STRING("jsm"); 1.1055 + event.mInnerID.Value().SetAsString() = frame.mFilename; 1.1056 + } 1.1057 + 1.1058 + event.mLevel = aData->mMethodString; 1.1059 + event.mFilename = frame.mFilename; 1.1060 + event.mLineNumber = frame.mLineNumber; 1.1061 + event.mFunctionName = frame.mFunctionName; 1.1062 + event.mTimeStamp = aData->mTimeStamp; 1.1063 + event.mPrivate = aData->mPrivate; 1.1064 + 1.1065 + switch (aData->mMethodName) { 1.1066 + case MethodLog: 1.1067 + case MethodInfo: 1.1068 + case MethodWarn: 1.1069 + case MethodError: 1.1070 + case MethodException: 1.1071 + case MethodDebug: 1.1072 + case MethodAssert: 1.1073 + event.mArguments.Construct(); 1.1074 + event.mStyles.Construct(); 1.1075 + ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), 1.1076 + event.mStyles.Value()); 1.1077 + break; 1.1078 + 1.1079 + default: 1.1080 + event.mArguments.Construct(); 1.1081 + ArgumentsToValueList(aData->mArguments, event.mArguments.Value()); 1.1082 + } 1.1083 + 1.1084 + if (aData->mMethodName == MethodGroup || 1.1085 + aData->mMethodName == MethodGroupCollapsed || 1.1086 + aData->mMethodName == MethodGroupEnd) { 1.1087 + ComposeGroupName(cx, aData->mArguments, event.mGroupName); 1.1088 + } 1.1089 + 1.1090 + else if (aData->mMethodName == MethodTime && !aData->mArguments.IsEmpty()) { 1.1091 + event.mTimer = StartTimer(cx, aData->mArguments[0], aData->mMonotonicTimer); 1.1092 + } 1.1093 + 1.1094 + else if (aData->mMethodName == MethodTimeEnd && !aData->mArguments.IsEmpty()) { 1.1095 + event.mTimer = StopTimer(cx, aData->mArguments[0], aData->mMonotonicTimer); 1.1096 + } 1.1097 + 1.1098 + else if (aData->mMethodName == MethodCount) { 1.1099 + event.mCounter = IncreaseCounter(cx, frame, aData->mArguments); 1.1100 + } 1.1101 + 1.1102 + // We want to create a console event object and pass it to our 1.1103 + // nsIConsoleAPIStorage implementation. We want to define some accessor 1.1104 + // properties on this object, and those will need to keep an nsIStackFrame 1.1105 + // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And 1.1106 + // further, passing untrusted objects to system code is likely to run afoul of 1.1107 + // Object Xrays. So we want to wrap in a system-principal scope here. But 1.1108 + // which one? We could cheat and try to get the underlying JSObject* of 1.1109 + // mStorage, but that's a bit fragile. Instead, we just use the junk scope, 1.1110 + // with explicit permission from the XPConnect module owner. If you're 1.1111 + // tempted to do that anywhere else, talk to said module owner first. 1.1112 + JSAutoCompartment ac2(cx, xpc::GetJunkScope()); 1.1113 + 1.1114 + JS::Rooted<JS::Value> eventValue(cx); 1.1115 + if (!event.ToObject(cx, &eventValue)) { 1.1116 + return; 1.1117 + } 1.1118 + 1.1119 + JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject()); 1.1120 + MOZ_ASSERT(eventObj); 1.1121 + 1.1122 + if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) { 1.1123 + return; 1.1124 + } 1.1125 + 1.1126 + if (ShouldIncludeStackrace(aData->mMethodName)) { 1.1127 + // Now define the "stacktrace" property on eventObj. There are two cases 1.1128 + // here. Either we came from a worker and have a reified stack, or we want 1.1129 + // to define a getter that will lazily reify the stack. 1.1130 + if (!aData->mReifiedStack.empty()) { 1.1131 + JS::Rooted<JS::Value> stacktrace(cx); 1.1132 + if (!ToJSValue(cx, aData->mReifiedStack.ref(), &stacktrace) || 1.1133 + !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace, 1.1134 + JSPROP_ENUMERATE)) { 1.1135 + return; 1.1136 + } 1.1137 + } else { 1.1138 + JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0, 1.1139 + eventObj, "stacktrace"); 1.1140 + if (!fun) { 1.1141 + return; 1.1142 + } 1.1143 + 1.1144 + JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun)); 1.1145 + 1.1146 + // We want to store our stack in the function and have it stay alive. But 1.1147 + // we also need sane access to the C++ nsIStackFrame. So store both a JS 1.1148 + // wrapper and the raw pointer: the former will keep the latter alive. 1.1149 + JS::Rooted<JS::Value> stackVal(cx); 1.1150 + nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack, 1.1151 + &stackVal); 1.1152 + if (NS_FAILED(rv)) { 1.1153 + return; 1.1154 + } 1.1155 + 1.1156 + js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal); 1.1157 + js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK, 1.1158 + JS::PrivateValue(aData->mStack.get())); 1.1159 + 1.1160 + if (!JS_DefineProperty(cx, eventObj, "stacktrace", 1.1161 + JS::UndefinedHandleValue, 1.1162 + JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER | 1.1163 + JSPROP_SETTER, 1.1164 + JS_DATA_TO_FUNC_PTR(JSPropertyOp, funObj.get()), 1.1165 + nullptr)) { 1.1166 + return; 1.1167 + } 1.1168 + } 1.1169 + } 1.1170 + 1.1171 + if (!mStorage) { 1.1172 + mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1"); 1.1173 + } 1.1174 + 1.1175 + if (!mStorage) { 1.1176 + NS_WARNING("Failed to get the ConsoleAPIStorage service."); 1.1177 + return; 1.1178 + } 1.1179 + 1.1180 + nsAutoString innerID; 1.1181 + innerID.AppendInt(mInnerID); 1.1182 + 1.1183 + if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) { 1.1184 + NS_WARNING("Failed to record a console event."); 1.1185 + } 1.1186 + 1.1187 + nsXPConnect* xpc = nsXPConnect::XPConnect(); 1.1188 + nsCOMPtr<nsISupports> wrapper; 1.1189 + const nsIID& iid = NS_GET_IID(nsISupports); 1.1190 + 1.1191 + if (NS_FAILED(xpc->WrapJS(cx, eventObj, iid, getter_AddRefs(wrapper)))) { 1.1192 + return; 1.1193 + } 1.1194 + 1.1195 + nsCOMPtr<nsIObserverService> obs = 1.1196 + do_GetService("@mozilla.org/observer-service;1"); 1.1197 + if (obs) { 1.1198 + nsAutoString outerID; 1.1199 + outerID.AppendInt(mOuterID); 1.1200 + 1.1201 + obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get()); 1.1202 + } 1.1203 +} 1.1204 + 1.1205 +void 1.1206 +Console::ProcessArguments(JSContext* aCx, 1.1207 + const nsTArray<JS::Heap<JS::Value>>& aData, 1.1208 + Sequence<JS::Value>& aSequence, 1.1209 + Sequence<JS::Value>& aStyles) 1.1210 +{ 1.1211 + if (aData.IsEmpty()) { 1.1212 + return; 1.1213 + } 1.1214 + 1.1215 + if (aData.Length() == 1 || !aData[0].isString()) { 1.1216 + ArgumentsToValueList(aData, aSequence); 1.1217 + return; 1.1218 + } 1.1219 + 1.1220 + JS::Rooted<JS::Value> format(aCx, aData[0]); 1.1221 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format)); 1.1222 + if (!jsString) { 1.1223 + return; 1.1224 + } 1.1225 + 1.1226 + nsDependentJSString string; 1.1227 + if (!string.init(aCx, jsString)) { 1.1228 + return; 1.1229 + } 1.1230 + 1.1231 + nsString::const_iterator start, end; 1.1232 + string.BeginReading(start); 1.1233 + string.EndReading(end); 1.1234 + 1.1235 + nsString output; 1.1236 + uint32_t index = 1; 1.1237 + 1.1238 + while (start != end) { 1.1239 + if (*start != '%') { 1.1240 + output.Append(*start); 1.1241 + ++start; 1.1242 + continue; 1.1243 + } 1.1244 + 1.1245 + ++start; 1.1246 + if (start == end) { 1.1247 + output.Append('%'); 1.1248 + break; 1.1249 + } 1.1250 + 1.1251 + if (*start == '%') { 1.1252 + output.Append(*start); 1.1253 + ++start; 1.1254 + continue; 1.1255 + } 1.1256 + 1.1257 + nsAutoString tmp; 1.1258 + tmp.Append('%'); 1.1259 + 1.1260 + int32_t integer = -1; 1.1261 + int32_t mantissa = -1; 1.1262 + 1.1263 + // Let's parse %<number>.<number> for %d and %f 1.1264 + if (*start >= '0' && *start <= '9') { 1.1265 + integer = 0; 1.1266 + 1.1267 + do { 1.1268 + integer = integer * 10 + *start - '0'; 1.1269 + tmp.Append(*start); 1.1270 + ++start; 1.1271 + } while (*start >= '0' && *start <= '9' && start != end); 1.1272 + } 1.1273 + 1.1274 + if (start == end) { 1.1275 + output.Append(tmp); 1.1276 + break; 1.1277 + } 1.1278 + 1.1279 + if (*start == '.') { 1.1280 + tmp.Append(*start); 1.1281 + ++start; 1.1282 + 1.1283 + if (start == end) { 1.1284 + output.Append(tmp); 1.1285 + break; 1.1286 + } 1.1287 + 1.1288 + // '.' must be followed by a number. 1.1289 + if (*start < '0' || *start > '9') { 1.1290 + output.Append(tmp); 1.1291 + continue; 1.1292 + } 1.1293 + 1.1294 + mantissa = 0; 1.1295 + 1.1296 + do { 1.1297 + mantissa = mantissa * 10 + *start - '0'; 1.1298 + tmp.Append(*start); 1.1299 + ++start; 1.1300 + } while (*start >= '0' && *start <= '9' && start != end); 1.1301 + 1.1302 + if (start == end) { 1.1303 + output.Append(tmp); 1.1304 + break; 1.1305 + } 1.1306 + } 1.1307 + 1.1308 + char ch = *start; 1.1309 + tmp.Append(ch); 1.1310 + ++start; 1.1311 + 1.1312 + switch (ch) { 1.1313 + case 'o': 1.1314 + case 'O': 1.1315 + { 1.1316 + if (!output.IsEmpty()) { 1.1317 + JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, 1.1318 + output.get(), 1.1319 + output.Length())); 1.1320 + if (!str) { 1.1321 + return; 1.1322 + } 1.1323 + 1.1324 + aSequence.AppendElement(JS::StringValue(str)); 1.1325 + output.Truncate(); 1.1326 + } 1.1327 + 1.1328 + JS::Rooted<JS::Value> v(aCx); 1.1329 + if (index < aData.Length()) { 1.1330 + v = aData[index++]; 1.1331 + } 1.1332 + 1.1333 + aSequence.AppendElement(v); 1.1334 + break; 1.1335 + } 1.1336 + 1.1337 + case 'c': 1.1338 + { 1.1339 + if (!output.IsEmpty()) { 1.1340 + JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, 1.1341 + output.get(), 1.1342 + output.Length())); 1.1343 + if (!str) { 1.1344 + return; 1.1345 + } 1.1346 + 1.1347 + aSequence.AppendElement(JS::StringValue(str)); 1.1348 + output.Truncate(); 1.1349 + } 1.1350 + 1.1351 + if (index < aData.Length()) { 1.1352 + JS::Rooted<JS::Value> v(aCx, aData[index++]); 1.1353 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v)); 1.1354 + if (!jsString) { 1.1355 + return; 1.1356 + } 1.1357 + 1.1358 + int32_t diff = aSequence.Length() - aStyles.Length(); 1.1359 + if (diff > 0) { 1.1360 + for (int32_t i = 0; i < diff; i++) { 1.1361 + aStyles.AppendElement(JS::NullValue()); 1.1362 + } 1.1363 + } 1.1364 + aStyles.AppendElement(JS::StringValue(jsString)); 1.1365 + } 1.1366 + break; 1.1367 + } 1.1368 + 1.1369 + case 's': 1.1370 + if (index < aData.Length()) { 1.1371 + JS::Rooted<JS::Value> value(aCx, aData[index++]); 1.1372 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 1.1373 + if (!jsString) { 1.1374 + return; 1.1375 + } 1.1376 + 1.1377 + nsDependentJSString v; 1.1378 + if (!v.init(aCx, jsString)) { 1.1379 + return; 1.1380 + } 1.1381 + 1.1382 + output.Append(v); 1.1383 + } 1.1384 + break; 1.1385 + 1.1386 + case 'd': 1.1387 + case 'i': 1.1388 + if (index < aData.Length()) { 1.1389 + JS::Rooted<JS::Value> value(aCx, aData[index++]); 1.1390 + 1.1391 + int32_t v; 1.1392 + if (!JS::ToInt32(aCx, value, &v)) { 1.1393 + return; 1.1394 + } 1.1395 + 1.1396 + nsCString format; 1.1397 + MakeFormatString(format, integer, mantissa, 'd'); 1.1398 + output.AppendPrintf(format.get(), v); 1.1399 + } 1.1400 + break; 1.1401 + 1.1402 + case 'f': 1.1403 + if (index < aData.Length()) { 1.1404 + JS::Rooted<JS::Value> value(aCx, aData[index++]); 1.1405 + 1.1406 + double v; 1.1407 + if (!JS::ToNumber(aCx, value, &v)) { 1.1408 + return; 1.1409 + } 1.1410 + 1.1411 + nsCString format; 1.1412 + MakeFormatString(format, integer, mantissa, 'f'); 1.1413 + output.AppendPrintf(format.get(), v); 1.1414 + } 1.1415 + break; 1.1416 + 1.1417 + default: 1.1418 + output.Append(tmp); 1.1419 + break; 1.1420 + } 1.1421 + } 1.1422 + 1.1423 + if (!output.IsEmpty()) { 1.1424 + JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(), 1.1425 + output.Length())); 1.1426 + if (!str) { 1.1427 + return; 1.1428 + } 1.1429 + 1.1430 + aSequence.AppendElement(JS::StringValue(str)); 1.1431 + } 1.1432 + 1.1433 + // The rest of the array, if unused by the format string. 1.1434 + for (; index < aData.Length(); ++index) { 1.1435 + aSequence.AppendElement(aData[index]); 1.1436 + } 1.1437 +} 1.1438 + 1.1439 +void 1.1440 +Console::MakeFormatString(nsCString& aFormat, int32_t aInteger, 1.1441 + int32_t aMantissa, char aCh) 1.1442 +{ 1.1443 + aFormat.Append("%"); 1.1444 + if (aInteger >= 0) { 1.1445 + aFormat.AppendInt(aInteger); 1.1446 + } 1.1447 + 1.1448 + if (aMantissa >= 0) { 1.1449 + aFormat.Append("."); 1.1450 + aFormat.AppendInt(aMantissa); 1.1451 + } 1.1452 + 1.1453 + aFormat.Append(aCh); 1.1454 +} 1.1455 + 1.1456 +void 1.1457 +Console::ComposeGroupName(JSContext* aCx, 1.1458 + const nsTArray<JS::Heap<JS::Value>>& aData, 1.1459 + nsAString& aName) 1.1460 +{ 1.1461 + for (uint32_t i = 0; i < aData.Length(); ++i) { 1.1462 + if (i != 0) { 1.1463 + aName.AppendASCII(" "); 1.1464 + } 1.1465 + 1.1466 + JS::Rooted<JS::Value> value(aCx, aData[i]); 1.1467 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 1.1468 + if (!jsString) { 1.1469 + return; 1.1470 + } 1.1471 + 1.1472 + nsDependentJSString string; 1.1473 + if (!string.init(aCx, jsString)) { 1.1474 + return; 1.1475 + } 1.1476 + 1.1477 + aName.Append(string); 1.1478 + } 1.1479 +} 1.1480 + 1.1481 +JS::Value 1.1482 +Console::StartTimer(JSContext* aCx, const JS::Value& aName, 1.1483 + DOMHighResTimeStamp aTimestamp) 1.1484 +{ 1.1485 + if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) { 1.1486 + RootedDictionary<ConsoleTimerError> error(aCx); 1.1487 + 1.1488 + JS::Rooted<JS::Value> value(aCx); 1.1489 + if (!error.ToObject(aCx, &value)) { 1.1490 + return JS::UndefinedValue(); 1.1491 + } 1.1492 + 1.1493 + return value; 1.1494 + } 1.1495 + 1.1496 + RootedDictionary<ConsoleTimerStart> timer(aCx); 1.1497 + 1.1498 + JS::Rooted<JS::Value> name(aCx, aName); 1.1499 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name)); 1.1500 + if (!jsString) { 1.1501 + return JS::UndefinedValue(); 1.1502 + } 1.1503 + 1.1504 + nsDependentJSString key; 1.1505 + if (!key.init(aCx, jsString)) { 1.1506 + return JS::UndefinedValue(); 1.1507 + } 1.1508 + 1.1509 + timer.mName = key; 1.1510 + 1.1511 + DOMHighResTimeStamp entry; 1.1512 + if (!mTimerRegistry.Get(key, &entry)) { 1.1513 + mTimerRegistry.Put(key, aTimestamp); 1.1514 + } else { 1.1515 + aTimestamp = entry; 1.1516 + } 1.1517 + 1.1518 + timer.mStarted = aTimestamp; 1.1519 + 1.1520 + JS::Rooted<JS::Value> value(aCx); 1.1521 + if (!timer.ToObject(aCx, &value)) { 1.1522 + return JS::UndefinedValue(); 1.1523 + } 1.1524 + 1.1525 + return value; 1.1526 +} 1.1527 + 1.1528 +JS::Value 1.1529 +Console::StopTimer(JSContext* aCx, const JS::Value& aName, 1.1530 + DOMHighResTimeStamp aTimestamp) 1.1531 +{ 1.1532 + JS::Rooted<JS::Value> name(aCx, aName); 1.1533 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name)); 1.1534 + if (!jsString) { 1.1535 + return JS::UndefinedValue(); 1.1536 + } 1.1537 + 1.1538 + nsDependentJSString key; 1.1539 + if (!key.init(aCx, jsString)) { 1.1540 + return JS::UndefinedValue(); 1.1541 + } 1.1542 + 1.1543 + DOMHighResTimeStamp entry; 1.1544 + if (!mTimerRegistry.Get(key, &entry)) { 1.1545 + return JS::UndefinedValue(); 1.1546 + } 1.1547 + 1.1548 + mTimerRegistry.Remove(key); 1.1549 + 1.1550 + RootedDictionary<ConsoleTimerEnd> timer(aCx); 1.1551 + timer.mName = key; 1.1552 + timer.mDuration = aTimestamp - entry; 1.1553 + 1.1554 + JS::Rooted<JS::Value> value(aCx); 1.1555 + if (!timer.ToObject(aCx, &value)) { 1.1556 + return JS::UndefinedValue(); 1.1557 + } 1.1558 + 1.1559 + return value; 1.1560 +} 1.1561 + 1.1562 +void 1.1563 +Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData, 1.1564 + Sequence<JS::Value>& aSequence) 1.1565 +{ 1.1566 + for (uint32_t i = 0; i < aData.Length(); ++i) { 1.1567 + aSequence.AppendElement(aData[i]); 1.1568 + } 1.1569 +} 1.1570 + 1.1571 +JS::Value 1.1572 +Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame, 1.1573 + const nsTArray<JS::Heap<JS::Value>>& aArguments) 1.1574 +{ 1.1575 + ClearException ce(aCx); 1.1576 + 1.1577 + nsAutoString key; 1.1578 + nsAutoString label; 1.1579 + 1.1580 + if (!aArguments.IsEmpty()) { 1.1581 + JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]); 1.1582 + JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue)); 1.1583 + 1.1584 + nsDependentJSString string; 1.1585 + if (jsString && string.init(aCx, jsString)) { 1.1586 + label = string; 1.1587 + key = string; 1.1588 + } 1.1589 + } 1.1590 + 1.1591 + if (key.IsEmpty()) { 1.1592 + key.Append(aFrame.mFilename); 1.1593 + key.Append(NS_LITERAL_STRING(":")); 1.1594 + key.AppendInt(aFrame.mLineNumber); 1.1595 + } 1.1596 + 1.1597 + uint32_t count = 0; 1.1598 + if (!mCounterRegistry.Get(key, &count)) { 1.1599 + if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) { 1.1600 + RootedDictionary<ConsoleCounterError> error(aCx); 1.1601 + 1.1602 + JS::Rooted<JS::Value> value(aCx); 1.1603 + if (!error.ToObject(aCx, &value)) { 1.1604 + return JS::UndefinedValue(); 1.1605 + } 1.1606 + 1.1607 + return value; 1.1608 + } 1.1609 + } 1.1610 + 1.1611 + ++count; 1.1612 + mCounterRegistry.Put(key, count); 1.1613 + 1.1614 + RootedDictionary<ConsoleCounter> data(aCx); 1.1615 + data.mLabel = label; 1.1616 + data.mCount = count; 1.1617 + 1.1618 + JS::Rooted<JS::Value> value(aCx); 1.1619 + if (!data.ToObject(aCx, &value)) { 1.1620 + return JS::UndefinedValue(); 1.1621 + } 1.1622 + 1.1623 + return value; 1.1624 +} 1.1625 + 1.1626 +void 1.1627 +Console::ClearConsoleData() 1.1628 +{ 1.1629 + while (ConsoleCallData* data = mQueuedCalls.popFirst()) { 1.1630 + delete data; 1.1631 + } 1.1632 +} 1.1633 + 1.1634 +bool 1.1635 +Console::ShouldIncludeStackrace(MethodName aMethodName) 1.1636 +{ 1.1637 + switch (aMethodName) { 1.1638 + case MethodError: 1.1639 + case MethodException: 1.1640 + case MethodAssert: 1.1641 + case MethodTrace: 1.1642 + return true; 1.1643 + default: 1.1644 + return false; 1.1645 + } 1.1646 +} 1.1647 + 1.1648 +} // namespace dom 1.1649 +} // namespace mozilla