Console.cpp (91377B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/Console.h" 8 9 #include "ConsoleCommon.h" 10 #include "js/Array.h" // JS::GetArrayLength, JS::NewArrayObject 11 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_GetElement 12 #include "mozilla/BasePrincipal.h" 13 #include "mozilla/HoldDropJSObjects.h" 14 #include "mozilla/JSObjectHolder.h" 15 #include "mozilla/Maybe.h" 16 #include "mozilla/Mutex.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/StaticPrefs_devtools.h" 19 #include "mozilla/StaticPrefs_dom.h" 20 #include "mozilla/dom/BlobBinding.h" 21 #include "mozilla/dom/BlobImpl.h" 22 #include "mozilla/dom/ConsoleBinding.h" 23 #include "mozilla/dom/ConsoleInstance.h" 24 #include "mozilla/dom/Document.h" 25 #include "mozilla/dom/ElementBinding.h" 26 #include "mozilla/dom/Exceptions.h" 27 #include "mozilla/dom/File.h" 28 #include "mozilla/dom/FunctionBinding.h" 29 #include "mozilla/dom/Performance.h" 30 #include "mozilla/dom/PromiseBinding.h" 31 #include "mozilla/dom/RootedDictionary.h" 32 #include "mozilla/dom/ScriptSettings.h" 33 #include "mozilla/dom/StructuredCloneHolder.h" 34 #include "mozilla/dom/ToJSValue.h" 35 #include "mozilla/dom/WorkerRunnable.h" 36 #include "mozilla/dom/WorkerScope.h" 37 #include "mozilla/dom/WorkletGlobalScope.h" 38 #include "mozilla/dom/WorkletImpl.h" 39 #include "mozilla/dom/WorkletThread.h" 40 #include "nsContentUtils.h" 41 #include "nsCycleCollectionParticipant.h" 42 #include "nsDOMNavigationTiming.h" 43 #include "nsDocShell.h" 44 #include "nsGlobalWindowInner.h" 45 #include "nsIConsoleAPIStorage.h" 46 #include "nsIException.h" // for nsIStackFrame 47 #include "nsIInterfaceRequestorUtils.h" 48 #include "nsILoadContext.h" 49 #include "nsISensitiveInfoHiddenURI.h" 50 #include "nsISupportsPrimitives.h" 51 #include "nsIWebNavigation.h" 52 #include "nsIXPConnect.h" 53 #include "nsJSUtils.h" 54 #include "nsNetUtil.h" 55 #include "nsProxyRelease.h" 56 #include "nsReadableUtils.h" 57 #include "xpcpublic.h" 58 59 // The maximum allowed number of concurrent timers per page. 60 #define MAX_PAGE_TIMERS 10000 61 62 // The maximum allowed number of concurrent counters per page. 63 #define MAX_PAGE_COUNTERS 10000 64 65 // The maximum stacktrace depth when populating the stacktrace array used for 66 // console.trace(). 67 #define DEFAULT_MAX_STACKTRACE_DEPTH 200 68 69 // This tags are used in the Structured Clone Algorithm to move js values from 70 // worker thread to main thread 71 #define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN 72 73 // This value is taken from ConsoleAPIStorage.js 74 #define STORAGE_MAX_EVENTS 1000 75 76 using namespace mozilla::dom::exceptions; 77 78 namespace mozilla::dom { 79 80 struct ConsoleStructuredCloneData { 81 nsCOMPtr<nsIGlobalObject> mGlobal; 82 nsTArray<RefPtr<BlobImpl>> mBlobs; 83 }; 84 85 static void ComposeAndStoreGroupName(JSContext* aCx, 86 const Sequence<JS::Value>& aData, 87 nsAString& aName, 88 nsTArray<nsString>* aGroupStack); 89 static bool UnstoreGroupName(nsAString& aName, nsTArray<nsString>* aGroupStack); 90 91 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData, 92 Sequence<JS::Value>& aSequence, 93 Sequence<nsString>& aStyles); 94 95 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx, 96 const nsAString& aCountLabel, 97 uint32_t aCountValue); 98 99 /** 100 * Console API in workers uses the Structured Clone Algorithm to move any value 101 * from the worker thread to the main-thread. Some object cannot be moved and, 102 * in these cases, we convert them to strings. 103 * It's not the best, but at least we are able to show something. 104 */ 105 106 class ConsoleCallData final { 107 public: 108 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData) 109 110 ConsoleCallData(Console::MethodName aName, const nsAString& aString, 111 Console* aConsole) 112 : mMutex("ConsoleCallData"), 113 mConsoleID(aConsole->mConsoleID), 114 mPrefix(aConsole->mPrefix), 115 mMethodName(aName), 116 mMicroSecondTimeStamp(JS_Now()), 117 mStartTimerValue(0), 118 mStartTimerStatus(Console::eTimerUnknown), 119 mLogTimerDuration(0), 120 mLogTimerStatus(Console::eTimerUnknown), 121 mCountValue(MAX_PAGE_COUNTERS), 122 mIDType(eUnknown), 123 mOuterIDNumber(0), 124 mInnerIDNumber(0), 125 mMethodString(aString) {} 126 127 void SetIDs(uint64_t aOuterID, uint64_t aInnerID) MOZ_REQUIRES(mMutex) { 128 MOZ_ASSERT(mIDType == eUnknown); 129 130 mOuterIDNumber = aOuterID; 131 mInnerIDNumber = aInnerID; 132 mIDType = eNumber; 133 } 134 135 void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) 136 MOZ_REQUIRES(mMutex) { 137 MOZ_ASSERT(mIDType == eUnknown); 138 139 mOuterIDString = aOuterID; 140 mInnerIDString = aInnerID; 141 mIDType = eString; 142 } 143 144 void SetOriginAttributes(const OriginAttributes& aOriginAttributes) 145 MOZ_REQUIRES(mMutex) { 146 mOriginAttributes = aOriginAttributes; 147 } 148 149 void SetAddonId(nsIPrincipal* aPrincipal) MOZ_REQUIRES(mMutex) { 150 nsAutoString addonId; 151 aPrincipal->GetAddonId(addonId); 152 153 mAddonId = addonId; 154 } 155 156 void AssertIsOnOwningThread() const { 157 NS_ASSERT_OWNINGTHREAD(ConsoleCallData); 158 } 159 160 Mutex mMutex; 161 162 const nsString mConsoleID MOZ_GUARDED_BY(mMutex); 163 const nsString mPrefix MOZ_GUARDED_BY(mMutex); 164 165 const Console::MethodName mMethodName MOZ_GUARDED_BY(mMutex); 166 int64_t mMicroSecondTimeStamp MOZ_GUARDED_BY(mMutex); 167 168 // These values are set in the owning thread and they contain the timestamp of 169 // when the new timer has started, the name of it and the status of the 170 // creation of it. If status is false, something went wrong. User 171 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use 172 // monotonicTimer from Performance.now(); 173 // They will be set on the owning thread and never touched again on that 174 // thread. They will be used in order to create a ConsoleTimerStart dictionary 175 // when console.time() is used. 176 DOMHighResTimeStamp mStartTimerValue MOZ_GUARDED_BY(mMutex); 177 nsString mStartTimerLabel MOZ_GUARDED_BY(mMutex); 178 Console::TimerStatus mStartTimerStatus MOZ_GUARDED_BY(mMutex); 179 180 // These values are set in the owning thread and they contain the duration, 181 // the name and the status of the LogTimer method. If status is false, 182 // something went wrong. They will be set on the owning thread and never 183 // touched again on that thread. They will be used in order to create a 184 // ConsoleTimerLogOrEnd dictionary. This members are set when 185 // console.timeEnd() or console.timeLog() are called. 186 double mLogTimerDuration MOZ_GUARDED_BY(mMutex); 187 nsString mLogTimerLabel MOZ_GUARDED_BY(mMutex); 188 Console::TimerStatus mLogTimerStatus MOZ_GUARDED_BY(mMutex); 189 190 // These 2 values are set by IncreaseCounter or ResetCounter on the owning 191 // thread and they are used by CreateCounterOrResetCounterValue. 192 // These members are set when console.count() or console.countReset() are 193 // called. 194 nsString mCountLabel MOZ_GUARDED_BY(mMutex); 195 uint32_t mCountValue MOZ_GUARDED_BY(mMutex); 196 197 // The concept of outerID and innerID is misleading because when a 198 // ConsoleCallData is created from a window, these are the window IDs, but 199 // when the object is created from a SharedWorker, a ServiceWorker or a 200 // subworker of a ChromeWorker these IDs are the type of worker and the 201 // filename of the callee. 202 // In Console.sys.mjs the ID is 'jsm'. 203 enum { eString, eNumber, eUnknown } mIDType MOZ_GUARDED_BY(mMutex); 204 205 uint64_t mOuterIDNumber MOZ_GUARDED_BY(mMutex); 206 nsString mOuterIDString MOZ_GUARDED_BY(mMutex); 207 208 uint64_t mInnerIDNumber MOZ_GUARDED_BY(mMutex); 209 nsString mInnerIDString MOZ_GUARDED_BY(mMutex); 210 211 OriginAttributes mOriginAttributes MOZ_GUARDED_BY(mMutex); 212 213 nsString mAddonId MOZ_GUARDED_BY(mMutex); 214 215 const nsString mMethodString MOZ_GUARDED_BY(mMutex); 216 217 // Stack management is complicated, because we want to do it as 218 // lazily as possible. Therefore, we have the following behavior: 219 // 1) mTopStackFrame is initialized whenever we have any JS on the stack 220 // 2) mReifiedStack is initialized if we're created in a worker. 221 // 3) mStack is set (possibly to null if there is no JS on the stack) if 222 // we're created on main thread. 223 Maybe<ConsoleStackEntry> mTopStackFrame MOZ_GUARDED_BY(mMutex); 224 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack MOZ_GUARDED_BY(mMutex); 225 nsCOMPtr<nsIStackFrame> mStack MOZ_GUARDED_BY(mMutex); 226 227 private: 228 ~ConsoleCallData() = default; 229 230 NS_DECL_OWNINGTHREAD; 231 }; 232 233 // MainThreadConsoleData instances are created on the Console thread and 234 // referenced from both main and Console threads in order to provide the same 235 // object for any ConsoleRunnables relating to the same Console. A Console 236 // owns a MainThreadConsoleData; MainThreadConsoleData does not keep its 237 // Console alive. 238 class MainThreadConsoleData final { 239 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData); 240 241 JSObject* GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal); 242 // This method must receive aCx and aArguments in the same JS::Compartment. 243 void ProcessCallData(JSContext* aCx, ConsoleCallData* aData, 244 const Sequence<JS::Value>& aArguments); 245 246 private: 247 ~MainThreadConsoleData() { 248 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage", 249 mStorage.forget()); 250 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox", 251 mSandbox.forget()); 252 } 253 254 // All members, except for mRefCnt, are accessed only on the main thread, 255 // except in MainThreadConsoleData destruction, at which point there are no 256 // other references. 257 nsCOMPtr<nsIConsoleAPIStorage> mStorage; 258 RefPtr<JSObjectHolder> mSandbox; 259 nsTArray<nsString> mGroupStack; 260 }; 261 262 // This base class must be extended for Worker and for Worklet. 263 class ConsoleRunnable : public StructuredCloneHolderBase { 264 public: 265 ~ConsoleRunnable() override { 266 MOZ_ASSERT(!mClonedData.mGlobal, 267 "mClonedData.mGlobal is set and cleared in a main thread scope"); 268 // Clear the StructuredCloneHolderBase class. 269 Clear(); 270 } 271 272 protected: 273 JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader, 274 const JS::CloneDataPolicy& aCloneDataPolicy, 275 uint32_t aTag, uint32_t aIndex) override { 276 AssertIsOnMainThread(); 277 278 if (aTag == CONSOLE_TAG_BLOB) { 279 MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex); 280 281 JS::Rooted<JS::Value> val(aCx); 282 { 283 nsCOMPtr<nsIGlobalObject> global = mClonedData.mGlobal; 284 RefPtr<Blob> blob = 285 Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex)); 286 if (!ToJSValue(aCx, blob, &val)) { 287 return nullptr; 288 } 289 } 290 291 return &val.toObject(); 292 } 293 294 MOZ_CRASH("No other tags are supported."); 295 return nullptr; 296 } 297 298 bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter, 299 JS::Handle<JSObject*> aObj, 300 bool* aSameProcessScopeRequired) override { 301 RefPtr<Blob> blob; 302 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) { 303 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB, 304 mClonedData.mBlobs.Length()))) { 305 return false; 306 } 307 308 mClonedData.mBlobs.AppendElement(blob->Impl()); 309 return true; 310 } 311 312 if (!JS_ObjectNotWritten(aWriter, aObj)) { 313 return false; 314 } 315 316 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj)); 317 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 318 if (NS_WARN_IF(!jsString)) { 319 return false; 320 } 321 322 if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) { 323 return false; 324 } 325 326 return true; 327 } 328 329 // Helper method for CallData 330 void ProcessCallData(JSContext* aCx, MainThreadConsoleData* aConsoleData, 331 ConsoleCallData* aCallData) { 332 AssertIsOnMainThread(); 333 334 ConsoleCommon::ClearException ce(aCx); 335 336 // This is the same policy as when writing from the other side, in 337 // WriteData. 338 JS::CloneDataPolicy cloneDataPolicy; 339 cloneDataPolicy.allowIntraClusterClonableSharedObjects(); 340 cloneDataPolicy.allowSharedMemoryObjects(); 341 342 JS::Rooted<JS::Value> argumentsValue(aCx); 343 if (!Read(aCx, &argumentsValue, cloneDataPolicy)) { 344 return; 345 } 346 347 MOZ_ASSERT(argumentsValue.isObject()); 348 349 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject()); 350 351 uint32_t length; 352 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) { 353 return; 354 } 355 356 Sequence<JS::Value> values; 357 SequenceRooter<JS::Value> arguments(aCx, &values); 358 359 for (uint32_t i = 0; i < length; ++i) { 360 JS::Rooted<JS::Value> value(aCx); 361 362 if (!JS_GetElement(aCx, argumentsObj, i, &value)) { 363 return; 364 } 365 366 if (!values.AppendElement(value, fallible)) { 367 return; 368 } 369 } 370 371 MOZ_ASSERT(values.Length() == length); 372 373 aConsoleData->ProcessCallData(aCx, aCallData, values); 374 } 375 376 // Generic 377 bool WriteArguments(JSContext* aCx, const Sequence<JS::Value>& aArguments) { 378 ConsoleCommon::ClearException ce(aCx); 379 380 JS::Rooted<JSObject*> arguments( 381 aCx, JS::NewArrayObject(aCx, aArguments.Length())); 382 if (NS_WARN_IF(!arguments)) { 383 return false; 384 } 385 386 JS::Rooted<JS::Value> arg(aCx); 387 for (uint32_t i = 0; i < aArguments.Length(); ++i) { 388 arg = aArguments[i]; 389 if (NS_WARN_IF( 390 !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) { 391 return false; 392 } 393 } 394 395 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments)); 396 return WriteData(aCx, value); 397 } 398 399 // Helper method for Profile calls 400 void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName, 401 const nsAString& aAction) { 402 AssertIsOnMainThread(); 403 404 ConsoleCommon::ClearException ce(aCx); 405 406 JS::Rooted<JS::Value> argumentsValue(aCx); 407 bool ok = Read(aCx, &argumentsValue); 408 mClonedData.mGlobal = nullptr; 409 410 if (!ok) { 411 return; 412 } 413 414 MOZ_ASSERT(argumentsValue.isObject()); 415 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject()); 416 if (NS_WARN_IF(!argumentsObj)) { 417 return; 418 } 419 420 uint32_t length; 421 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) { 422 return; 423 } 424 425 Sequence<JS::Value> arguments; 426 427 for (uint32_t i = 0; i < length; ++i) { 428 JS::Rooted<JS::Value> value(aCx); 429 430 if (!JS_GetElement(aCx, argumentsObj, i, &value)) { 431 return; 432 } 433 434 if (!arguments.AppendElement(value, fallible)) { 435 return; 436 } 437 } 438 439 Console::ProfileMethodMainthread(aCx, aAction, arguments); 440 } 441 442 bool WriteData(JSContext* aCx, JS::Handle<JS::Value> aValue) { 443 // We use structuredClone to send the JSValue to the main-thread, in order 444 // to store it into the Console API Service. The consumer will be the 445 // console panel in the devtools and, because of this, we want to allow the 446 // cloning of sharedArrayBuffers and WASM modules. 447 JS::CloneDataPolicy cloneDataPolicy; 448 cloneDataPolicy.allowIntraClusterClonableSharedObjects(); 449 cloneDataPolicy.allowSharedMemoryObjects(); 450 451 if (NS_WARN_IF( 452 !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) { 453 // Ignore the message. 454 return false; 455 } 456 457 return true; 458 } 459 460 ConsoleStructuredCloneData mClonedData; 461 }; 462 463 class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable { 464 protected: 465 explicit ConsoleWorkletRunnable(Console* aConsole) 466 : Runnable("dom::console::ConsoleWorkletRunnable"), 467 mConsoleData(aConsole->GetOrCreateMainThreadData()) { 468 WorkletThread::AssertIsOnWorkletThread(); 469 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(aConsole->mGlobal); 470 MOZ_ASSERT(global); 471 mWorkletImpl = global->Impl(); 472 MOZ_ASSERT(mWorkletImpl); 473 } 474 475 ~ConsoleWorkletRunnable() override = default; 476 477 protected: 478 RefPtr<MainThreadConsoleData> mConsoleData; 479 480 RefPtr<WorkletImpl> mWorkletImpl; 481 }; 482 483 // This runnable appends a CallData object into the Console queue running on 484 // the main-thread. 485 class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable { 486 public: 487 static already_AddRefed<ConsoleCallDataWorkletRunnable> Create( 488 JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData, 489 const Sequence<JS::Value>& aArguments) { 490 WorkletThread::AssertIsOnWorkletThread(); 491 492 RefPtr<ConsoleCallDataWorkletRunnable> runnable = 493 new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData); 494 495 if (!runnable->WriteArguments(aCx, aArguments)) { 496 return nullptr; 497 } 498 499 return runnable.forget(); 500 } 501 502 private: 503 ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData) 504 : ConsoleWorkletRunnable(aConsole), mCallData(aCallData) { 505 WorkletThread::AssertIsOnWorkletThread(); 506 MOZ_ASSERT(aCallData); 507 aCallData->AssertIsOnOwningThread(); 508 509 const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo(); 510 mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID()); 511 } 512 513 ~ConsoleCallDataWorkletRunnable() override = default; 514 515 NS_IMETHOD Run() override { 516 AssertIsOnMainThread(); 517 AutoJSAPI jsapi; 518 jsapi.Init(); 519 JSContext* cx = jsapi.cx(); 520 521 { 522 MutexAutoLock lock(mCallData->mMutex); 523 524 JSObject* sandbox = 525 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal()); 526 JS::Rooted<JSObject*> global(cx, sandbox); 527 if (NS_WARN_IF(!global)) { 528 return NS_ERROR_FAILURE; 529 } 530 531 // The CreateSandbox call returns a proxy to the actual sandbox object. We 532 // don't need a proxy here. 533 global = js::UncheckedUnwrap(global); 534 JSAutoRealm ar(cx, global); 535 536 // We don't need to set a parent object in mCallData bacause there are not 537 // DOM objects exposed to worklet. 538 539 ProcessCallData(cx, mConsoleData, mCallData); 540 } 541 542 return NS_OK; 543 } 544 545 RefPtr<ConsoleCallData> mCallData; 546 }; 547 548 class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable, 549 public ConsoleRunnable { 550 public: 551 explicit ConsoleWorkerRunnable(Console* aConsole) 552 : mConsoleData(aConsole->GetOrCreateMainThreadData()) {} 553 554 ~ConsoleWorkerRunnable() override = default; 555 556 bool Dispatch(JSContext* aCx, const Sequence<JS::Value>& aArguments) { 557 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 558 MOZ_ASSERT(workerPrivate); 559 560 if (NS_WARN_IF(!WriteArguments(aCx, aArguments))) { 561 RunBackOnWorkerThreadForCleanup(workerPrivate); 562 return false; 563 } 564 565 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) { 566 // RunBackOnWorkerThreadForCleanup() will be called by 567 // WorkerProxyToMainThreadRunnable::Dispatch(). 568 return false; 569 } 570 571 return true; 572 } 573 574 protected: 575 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override { 576 MOZ_ASSERT(aWorkerPrivate); 577 AssertIsOnMainThread(); 578 579 // Walk up to our containing page 580 WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker(); 581 582 nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow(); 583 if (!window) { 584 RunWindowless(aWorkerPrivate); 585 } else { 586 RunWithWindow(aWorkerPrivate, window); 587 } 588 } 589 590 void RunWithWindow(WorkerPrivate* aWorkerPrivate, 591 nsPIDOMWindowInner* aWindow) { 592 MOZ_ASSERT(aWorkerPrivate); 593 AssertIsOnMainThread(); 594 595 AutoJSAPI jsapi; 596 MOZ_ASSERT(aWindow); 597 598 RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow); 599 if (NS_WARN_IF(!jsapi.Init(win))) { 600 return; 601 } 602 603 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow(); 604 if (NS_WARN_IF(!outerWindow)) { 605 return; 606 } 607 608 RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow, 609 aWindow); 610 } 611 612 void RunWindowless(WorkerPrivate* aWorkerPrivate) { 613 MOZ_ASSERT(aWorkerPrivate); 614 AssertIsOnMainThread(); 615 616 WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker(); 617 618 MOZ_ASSERT(!wp->GetWindow()); 619 620 AutoJSAPI jsapi; 621 jsapi.Init(); 622 623 JSContext* cx = jsapi.cx(); 624 625 JS::Rooted<JSObject*> global( 626 cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal())); 627 if (NS_WARN_IF(!global)) { 628 return; 629 } 630 631 // The GetOrCreateSandbox call returns a proxy to the actual sandbox object. 632 // We don't need a proxy here. 633 global = js::UncheckedUnwrap(global); 634 635 JSAutoRealm ar(cx, global); 636 637 nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global); 638 if (NS_WARN_IF(!globalObject)) { 639 return; 640 } 641 642 RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr); 643 } 644 645 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override { 646 MOZ_ASSERT(aWorkerPrivate); 647 aWorkerPrivate->AssertIsOnWorkerThread(); 648 } 649 650 // This method is called in the main-thread. 651 virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal, 652 WorkerPrivate* aWorkerPrivate, 653 nsPIDOMWindowOuter* aOuterWindow, 654 nsPIDOMWindowInner* aInnerWindow) = 0; 655 656 bool ForMessaging() const override { return true; } 657 658 RefPtr<MainThreadConsoleData> mConsoleData; 659 }; 660 661 // This runnable appends a CallData object into the Console queue running on 662 // the main-thread. 663 class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable { 664 public: 665 ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData) 666 : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) { 667 MOZ_ASSERT(aCallData); 668 mCallData->AssertIsOnOwningThread(); 669 } 670 671 private: 672 ~ConsoleCallDataWorkerRunnable() override = default; 673 674 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal, 675 WorkerPrivate* aWorkerPrivate, 676 nsPIDOMWindowOuter* aOuterWindow, 677 nsPIDOMWindowInner* aInnerWindow) override { 678 MOZ_ASSERT(aGlobal); 679 MOZ_ASSERT(aWorkerPrivate); 680 AssertIsOnMainThread(); 681 682 // The windows have to run in parallel. 683 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow); 684 685 { 686 MutexAutoLock lock(mCallData->mMutex); 687 if (aOuterWindow) { 688 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID()); 689 } else { 690 ConsoleStackEntry frame; 691 if (mCallData->mTopStackFrame) { 692 frame = *mCallData->mTopStackFrame; 693 } 694 695 nsCString id = frame.mFilename; 696 nsString innerID; 697 if (aWorkerPrivate->IsSharedWorker()) { 698 innerID = u"SharedWorker"_ns; 699 } else if (aWorkerPrivate->IsServiceWorker()) { 700 innerID = u"ServiceWorker"_ns; 701 // Use scope as ID so the webconsole can decide if the message should 702 // show up per tab 703 id = aWorkerPrivate->ServiceWorkerScope(); 704 } else { 705 innerID = u"Worker"_ns; 706 } 707 708 mCallData->SetIDs(NS_ConvertUTF8toUTF16(id), innerID); 709 } 710 711 mClonedData.mGlobal = aGlobal; 712 713 ProcessCallData(aCx, mConsoleData, mCallData); 714 715 mClonedData.mGlobal = nullptr; 716 } 717 } 718 719 RefPtr<ConsoleCallData> mCallData; 720 }; 721 722 // This runnable calls ProfileMethod() on the console on the main-thread. 723 class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable { 724 public: 725 static already_AddRefed<ConsoleProfileWorkletRunnable> Create( 726 JSContext* aCx, Console* aConsole, Console::MethodName aName, 727 const nsAString& aAction, const Sequence<JS::Value>& aArguments) { 728 WorkletThread::AssertIsOnWorkletThread(); 729 730 RefPtr<ConsoleProfileWorkletRunnable> runnable = 731 new ConsoleProfileWorkletRunnable(aConsole, aName, aAction); 732 733 if (!runnable->WriteArguments(aCx, aArguments)) { 734 return nullptr; 735 } 736 737 return runnable.forget(); 738 } 739 740 private: 741 ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName, 742 const nsAString& aAction) 743 : ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) { 744 MOZ_ASSERT(aConsole); 745 } 746 747 NS_IMETHOD Run() override { 748 AssertIsOnMainThread(); 749 750 AutoJSAPI jsapi; 751 jsapi.Init(); 752 JSContext* cx = jsapi.cx(); 753 754 JSObject* sandbox = 755 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal()); 756 JS::Rooted<JSObject*> global(cx, sandbox); 757 if (NS_WARN_IF(!global)) { 758 return NS_ERROR_FAILURE; 759 } 760 761 // The CreateSandbox call returns a proxy to the actual sandbox object. We 762 // don't need a proxy here. 763 global = js::UncheckedUnwrap(global); 764 765 JSAutoRealm ar(cx, global); 766 767 // We don't need to set a parent object in mCallData bacause there are not 768 // DOM objects exposed to worklet. 769 ProcessProfileData(cx, mName, mAction); 770 771 return NS_OK; 772 } 773 774 Console::MethodName mName; 775 nsString mAction; 776 }; 777 778 // This runnable calls ProfileMethod() on the console on the main-thread. 779 class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable { 780 public: 781 ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName, 782 const nsAString& aAction) 783 : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) { 784 MOZ_ASSERT(aConsole); 785 } 786 787 private: 788 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal, 789 WorkerPrivate* aWorkerPrivate, 790 nsPIDOMWindowOuter* aOuterWindow, 791 nsPIDOMWindowInner* aInnerWindow) override { 792 AssertIsOnMainThread(); 793 MOZ_ASSERT(aGlobal); 794 795 mClonedData.mGlobal = aGlobal; 796 797 ProcessProfileData(aCx, mName, mAction); 798 799 mClonedData.mGlobal = nullptr; 800 } 801 802 Console::MethodName mName; 803 nsString mAction; 804 }; 805 806 NS_IMPL_CYCLE_COLLECTION_CLASS(Console) 807 808 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console) 809 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) 810 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier) 811 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction) 812 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 813 tmp->Shutdown(); 814 tmp->mArgumentStorage.clearAndFree(); 815 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 816 817 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console) 818 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) 819 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier) 820 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction) 821 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 822 823 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console) 824 for (uint32_t i = 0; i < tmp->mArgumentStorage.length(); ++i) { 825 tmp->mArgumentStorage[i].Trace(aCallbacks, aClosure); 826 } 827 NS_IMPL_CYCLE_COLLECTION_TRACE_END 828 829 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console) 830 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console) 831 832 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console) 833 NS_INTERFACE_MAP_ENTRY(nsIObserver) 834 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) 835 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 836 NS_INTERFACE_MAP_END 837 838 /* static */ 839 already_AddRefed<Console> Console::Create(JSContext* aCx, 840 nsPIDOMWindowInner* aWindow, 841 ErrorResult& aRv) { 842 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow); 843 844 uint64_t outerWindowID = 0; 845 uint64_t innerWindowID = 0; 846 847 if (aWindow) { 848 innerWindowID = aWindow->WindowID(); 849 850 // Without outerwindow any console message coming from this object will not 851 // shown in the devtools webconsole. But this should be fine because 852 // probably we are shutting down, or the window is CCed/GCed. 853 nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow(); 854 if (outerWindow) { 855 outerWindowID = outerWindow->WindowID(); 856 } 857 } 858 859 RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow), 860 outerWindowID, innerWindowID); 861 console->Initialize(aRv); 862 if (NS_WARN_IF(aRv.Failed())) { 863 return nullptr; 864 } 865 866 return console.forget(); 867 } 868 869 /* static */ 870 already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx, 871 nsIGlobalObject* aGlobal, 872 uint64_t aOuterWindowID, 873 uint64_t aInnerWindowID, 874 ErrorResult& aRv) { 875 WorkletThread::AssertIsOnWorkletThread(); 876 877 RefPtr<Console> console = 878 new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID); 879 console->Initialize(aRv); 880 if (NS_WARN_IF(aRv.Failed())) { 881 return nullptr; 882 } 883 884 return console.forget(); 885 } 886 887 Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal, 888 uint64_t aOuterWindowID, uint64_t aInnerWindowID, 889 const nsAString& aPrefix) 890 : mGlobal(aGlobal), 891 mOuterID(aOuterWindowID), 892 mInnerID(aInnerWindowID), 893 mDumpToStdout(false), 894 mLogModule(nullptr), 895 mPrefix(aPrefix), 896 mChromeInstance(false), 897 mCurrentLogLevel(WebIDLLogLevelToInteger(ConsoleLogLevel::All)), 898 mStatus(eUnknown), 899 mCreationTimeStamp(TimeStamp::Now()) { 900 // Let's enable the dumping to stdout by default for chrome. 901 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) { 902 mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome(); 903 } else { 904 mDumpToStdout = StaticPrefs::devtools_console_stdout_content(); 905 } 906 907 // By default, the console uses "console" MOZ_LOG module name, 908 // but ConsoleInstance may pass a custom prefix which we will use a module 909 // name. 910 mLogModule = mPrefix.IsEmpty() 911 ? LogModule::Get("console") 912 : LogModule::Get(NS_ConvertUTF16toUTF8(mPrefix).get()); 913 914 mozilla::HoldJSObjects(this); 915 } 916 917 Console::~Console() { 918 AssertIsOnOwningThread(); 919 Shutdown(); 920 mozilla::DropJSObjects(this); 921 } 922 923 void Console::Initialize(ErrorResult& aRv) { 924 AssertIsOnOwningThread(); 925 MOZ_ASSERT(mStatus == eUnknown); 926 927 if (NS_IsMainThread()) { 928 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 929 if (NS_WARN_IF(!obs)) { 930 aRv.Throw(NS_ERROR_FAILURE); 931 return; 932 } 933 934 if (mInnerID) { 935 aRv = obs->AddObserver(this, "inner-window-destroyed", true); 936 if (NS_WARN_IF(aRv.Failed())) { 937 return; 938 } 939 } 940 941 aRv = obs->AddObserver(this, "memory-pressure", true); 942 if (NS_WARN_IF(aRv.Failed())) { 943 return; 944 } 945 } 946 947 mStatus = eInitialized; 948 } 949 950 void Console::Shutdown() { 951 AssertIsOnOwningThread(); 952 953 if (mStatus == eUnknown || mStatus == eShuttingDown) { 954 return; 955 } 956 957 if (NS_IsMainThread()) { 958 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 959 if (obs) { 960 obs->RemoveObserver(this, "inner-window-destroyed"); 961 obs->RemoveObserver(this, "memory-pressure"); 962 } 963 } 964 965 mTimerRegistry.Clear(); 966 mCounterRegistry.Clear(); 967 968 ClearStorage(); 969 mCallDataStorage.Clear(); 970 971 mStatus = eShuttingDown; 972 } 973 974 NS_IMETHODIMP 975 Console::Observe(nsISupports* aSubject, const char* aTopic, 976 const char16_t* aData) { 977 AssertIsOnMainThread(); 978 979 if (!strcmp(aTopic, "inner-window-destroyed")) { 980 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); 981 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); 982 983 uint64_t innerID; 984 nsresult rv = wrapper->GetData(&innerID); 985 NS_ENSURE_SUCCESS(rv, rv); 986 987 if (innerID == mInnerID) { 988 Shutdown(); 989 } 990 991 return NS_OK; 992 } 993 994 if (!strcmp(aTopic, "memory-pressure")) { 995 ClearStorage(); 996 return NS_OK; 997 } 998 999 return NS_OK; 1000 } 1001 1002 void Console::ClearStorage() { 1003 mCallDataStorage.Clear(); 1004 mArgumentStorage.clearAndFree(); 1005 } 1006 1007 #define METHOD(name, string) \ 1008 /* static */ void Console::name(const GlobalObject& aGlobal, \ 1009 const Sequence<JS::Value>& aData) { \ 1010 Method(aGlobal, Method##name, nsLiteralString(string), aData); \ 1011 } 1012 1013 METHOD(Log, u"log") 1014 METHOD(Info, u"info") 1015 METHOD(Warn, u"warn") 1016 METHOD(Error, u"error") 1017 METHOD(Exception, u"exception") 1018 METHOD(Debug, u"debug") 1019 METHOD(Table, u"table") 1020 METHOD(Trace, u"trace") 1021 1022 // Displays an interactive listing of all the properties of an object. 1023 METHOD(Dir, u"dir"); 1024 METHOD(Dirxml, u"dirxml"); 1025 1026 METHOD(Group, u"group") 1027 METHOD(GroupCollapsed, u"groupCollapsed") 1028 1029 #undef METHOD 1030 1031 /* static */ 1032 void Console::Clear(const GlobalObject& aGlobal) { 1033 const Sequence<JS::Value> data; 1034 Method(aGlobal, MethodClear, u"clear"_ns, data); 1035 } 1036 1037 /* static */ 1038 void Console::GroupEnd(const GlobalObject& aGlobal) { 1039 const Sequence<JS::Value> data; 1040 Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data); 1041 } 1042 1043 /* static */ 1044 void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) { 1045 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns); 1046 } 1047 1048 /* static */ 1049 void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) { 1050 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd, 1051 u"timeEnd"_ns); 1052 } 1053 1054 /* static */ 1055 void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel, 1056 const Sequence<JS::Value>& aData) { 1057 StringMethod(aGlobal, aLabel, aData, MethodTimeLog, u"timeLog"_ns); 1058 } 1059 1060 /* static */ 1061 void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel, 1062 const Sequence<JS::Value>& aData, 1063 MethodName aMethodName, 1064 const nsAString& aMethodString) { 1065 RefPtr<Console> console = GetConsole(aGlobal); 1066 if (!console) { 1067 return; 1068 } 1069 1070 console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName, 1071 aMethodString); 1072 } 1073 1074 void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel, 1075 const Sequence<JS::Value>& aData, 1076 MethodName aMethodName, 1077 const nsAString& aMethodString) { 1078 ConsoleCommon::ClearException ce(aCx); 1079 1080 Sequence<JS::Value> data; 1081 SequenceRooter<JS::Value> rooter(aCx, &data); 1082 1083 JS::Rooted<JS::Value> value(aCx); 1084 if (!dom::ToJSValue(aCx, aLabel, &value)) { 1085 return; 1086 } 1087 1088 if (!data.AppendElement(value, fallible)) { 1089 return; 1090 } 1091 1092 for (uint32_t i = 0; i < aData.Length(); ++i) { 1093 if (!data.AppendElement(aData[i], fallible)) { 1094 return; 1095 } 1096 } 1097 1098 MethodInternal(aCx, aMethodName, aMethodString, data); 1099 } 1100 1101 /* static */ 1102 void Console::TimeStamp(const GlobalObject& aGlobal, 1103 const JS::Handle<JS::Value> aData) { 1104 JSContext* cx = aGlobal.Context(); 1105 1106 ConsoleCommon::ClearException ce(cx); 1107 1108 Sequence<JS::Value> data; 1109 SequenceRooter<JS::Value> rooter(cx, &data); 1110 1111 if (aData.isString() && !data.AppendElement(aData, fallible)) { 1112 return; 1113 } 1114 1115 Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data); 1116 } 1117 1118 /* static */ 1119 void Console::Profile(const GlobalObject& aGlobal, 1120 const Sequence<JS::Value>& aData) { 1121 ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData); 1122 } 1123 1124 /* static */ 1125 void Console::ProfileEnd(const GlobalObject& aGlobal, 1126 const Sequence<JS::Value>& aData) { 1127 ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData); 1128 } 1129 1130 /* static */ 1131 void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName, 1132 const nsAString& aAction, 1133 const Sequence<JS::Value>& aData) { 1134 RefPtr<Console> console = GetConsole(aGlobal); 1135 if (!console) { 1136 return; 1137 } 1138 1139 JSContext* cx = aGlobal.Context(); 1140 console->ProfileMethodInternal(cx, aName, aAction, aData); 1141 } 1142 1143 void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName, 1144 const nsAString& aAction, 1145 const Sequence<JS::Value>& aData) { 1146 if (!ShouldProceed(aMethodName)) { 1147 return; 1148 } 1149 1150 MaybeExecuteDumpFunction(aCx, aMethodName, aAction, aData, nullptr, 1151 DOMHighResTimeStamp(0.0)); 1152 1153 if (WorkletThread::IsOnWorkletThread()) { 1154 RefPtr<ConsoleProfileWorkletRunnable> runnable = 1155 ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction, 1156 aData); 1157 if (!runnable) { 1158 return; 1159 } 1160 1161 NS_DispatchToMainThread(runnable.forget()); 1162 return; 1163 } 1164 1165 if (!NS_IsMainThread()) { 1166 // Here we are in a worker thread. 1167 RefPtr<ConsoleProfileWorkerRunnable> runnable = 1168 new ConsoleProfileWorkerRunnable(this, aMethodName, aAction); 1169 1170 runnable->Dispatch(aCx, aData); 1171 return; 1172 } 1173 1174 ProfileMethodMainthread(aCx, aAction, aData); 1175 } 1176 1177 // static 1178 void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction, 1179 const Sequence<JS::Value>& aData) { 1180 MOZ_ASSERT(NS_IsMainThread()); 1181 ConsoleCommon::ClearException ce(aCx); 1182 1183 RootedDictionary<ConsoleProfileEvent> event(aCx); 1184 event.mAction = aAction; 1185 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx); 1186 1187 event.mArguments.Construct(); 1188 Sequence<JS::Value>& sequence = event.mArguments.Value(); 1189 1190 for (uint32_t i = 0; i < aData.Length(); ++i) { 1191 if (!sequence.AppendElement(aData[i], fallible)) { 1192 return; 1193 } 1194 } 1195 1196 JS::Rooted<JS::Value> eventValue(aCx); 1197 if (!ToJSValue(aCx, event, &eventValue)) { 1198 return; 1199 } 1200 1201 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject()); 1202 MOZ_ASSERT(eventObj); 1203 1204 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, 1205 JSPROP_ENUMERATE)) { 1206 return; 1207 } 1208 1209 nsIXPConnect* xpc = nsContentUtils::XPConnect(); 1210 nsCOMPtr<nsISupports> wrapper; 1211 const nsIID& iid = NS_GET_IID(nsISupports); 1212 1213 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) { 1214 return; 1215 } 1216 1217 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1218 if (obs) { 1219 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr); 1220 } 1221 } 1222 1223 /* static */ 1224 void Console::Assert(const GlobalObject& aGlobal, bool aCondition, 1225 const Sequence<JS::Value>& aData) { 1226 if (!aCondition) { 1227 Method(aGlobal, MethodAssert, u"assert"_ns, aData); 1228 } 1229 } 1230 1231 /* static */ 1232 void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) { 1233 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount, 1234 u"count"_ns); 1235 } 1236 1237 /* static */ 1238 void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) { 1239 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset, 1240 u"countReset"_ns); 1241 } 1242 1243 namespace { 1244 1245 void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame, 1246 ConsoleStackEntry& aStackEntry) { 1247 MOZ_ASSERT(aStackFrame); 1248 1249 aStackFrame->GetFilename(aCx, aStackEntry.mFilename); 1250 aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx); 1251 aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx); 1252 aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx); 1253 1254 aStackFrame->GetName(aCx, aStackEntry.mFunctionName); 1255 1256 nsString cause; 1257 aStackFrame->GetAsyncCause(aCx, cause); 1258 if (!cause.IsEmpty()) { 1259 aStackEntry.mAsyncCause.Construct(cause); 1260 } 1261 } 1262 1263 void ReifyStack(JSContext* aCx, nsIStackFrame* aStack, 1264 nsTArray<ConsoleStackEntry>& aRefiedStack) { 1265 nsCOMPtr<nsIStackFrame> stack(aStack); 1266 1267 while (stack) { 1268 ConsoleStackEntry& data = *aRefiedStack.AppendElement(); 1269 StackFrameToStackEntry(aCx, stack, data); 1270 1271 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx); 1272 1273 if (!caller) { 1274 caller = stack->GetAsyncCaller(aCx); 1275 } 1276 stack.swap(caller); 1277 } 1278 } 1279 1280 } // anonymous namespace 1281 1282 // Queue a call to a console method. See the CALL_DELAY constant. 1283 /* static */ 1284 void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName, 1285 const nsAString& aMethodString, 1286 const Sequence<JS::Value>& aData) { 1287 RefPtr<Console> console = GetConsole(aGlobal); 1288 if (!console) { 1289 return; 1290 } 1291 1292 console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData); 1293 } 1294 1295 void Console::MethodInternal(JSContext* aCx, MethodName aMethodName, 1296 const nsAString& aMethodString, 1297 const Sequence<JS::Value>& aData) { 1298 if (!ShouldProceed(aMethodName)) { 1299 return; 1300 } 1301 1302 AssertIsOnOwningThread(); 1303 1304 ConsoleCommon::ClearException ce(aCx); 1305 1306 RefPtr<ConsoleCallData> callData = 1307 new ConsoleCallData(aMethodName, aMethodString, this); 1308 1309 MutexAutoLock lock(callData->mMutex); 1310 1311 if (!StoreCallData(aCx, callData, aData)) { 1312 return; 1313 } 1314 1315 OriginAttributes oa; 1316 1317 if (NS_IsMainThread()) { 1318 if (mGlobal) { 1319 // Save the principal's OriginAttributes in the console event data 1320 // so that we will be able to filter messages by origin attributes. 1321 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal); 1322 if (NS_WARN_IF(!sop)) { 1323 return; 1324 } 1325 1326 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); 1327 if (NS_WARN_IF(!principal)) { 1328 return; 1329 } 1330 1331 oa = principal->OriginAttributesRef(); 1332 callData->SetAddonId(principal); 1333 1334 #ifdef DEBUG 1335 if (!principal->IsSystemPrincipal()) { 1336 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal); 1337 if (webNav) { 1338 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav); 1339 MOZ_ASSERT(loadContext); 1340 1341 bool pb; 1342 if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) { 1343 MOZ_ASSERT(pb == oa.IsPrivateBrowsing()); 1344 } 1345 } 1346 } 1347 #endif 1348 } 1349 } else if (WorkletThread::IsOnWorkletThread()) { 1350 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal); 1351 MOZ_ASSERT(global); 1352 oa = global->Impl()->OriginAttributesRef(); 1353 } else { 1354 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 1355 MOZ_ASSERT(workerPrivate); 1356 oa = workerPrivate->GetOriginAttributes(); 1357 } 1358 1359 callData->SetOriginAttributes(oa); 1360 1361 JS::StackCapture captureMode = 1362 ShouldIncludeStackTrace(aMethodName) 1363 ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH)) 1364 : JS::StackCapture(JS::FirstSubsumedFrame(aCx)); 1365 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode)); 1366 1367 if (stack) { 1368 callData->mTopStackFrame.emplace(); 1369 StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame); 1370 } 1371 1372 if (NS_IsMainThread()) { 1373 callData->mStack = stack; 1374 } else { 1375 // nsIStackFrame is not threadsafe, so we need to snapshot it now, 1376 // before we post our runnable to the main thread. 1377 callData->mReifiedStack.emplace(); 1378 ReifyStack(aCx, stack, *callData->mReifiedStack); 1379 } 1380 1381 DOMHighResTimeStamp monotonicTimer = 0.0; 1382 1383 // Monotonic timer for 'time', 'timeLog' and 'timeEnd' 1384 if ((aMethodName == MethodTime || aMethodName == MethodTimeLog || 1385 aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) && 1386 !MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) { 1387 return; 1388 } 1389 1390 if (aMethodName == MethodTime && !aData.IsEmpty()) { 1391 callData->mStartTimerStatus = 1392 StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel, 1393 &callData->mStartTimerValue); 1394 } 1395 1396 else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) { 1397 callData->mLogTimerStatus = 1398 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel, 1399 &callData->mLogTimerDuration, true /* Cancel timer */); 1400 } 1401 1402 else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) { 1403 callData->mLogTimerStatus = 1404 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel, 1405 &callData->mLogTimerDuration, false /* Cancel timer */); 1406 } 1407 1408 else if (aMethodName == MethodCount) { 1409 callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel); 1410 if (!callData->mCountValue) { 1411 return; 1412 } 1413 } 1414 1415 else if (aMethodName == MethodCountReset) { 1416 callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel); 1417 if (callData->mCountLabel.IsEmpty()) { 1418 return; 1419 } 1420 } 1421 1422 // Before processing this CallData differently, it's time to call the dump 1423 // function. 1424 // 1425 // Only log the stack trace for console.trace() and console.assert() 1426 if (aMethodName == MethodTrace || aMethodName == MethodAssert) { 1427 MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, stack, 1428 monotonicTimer); 1429 } else { 1430 MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, nullptr, 1431 monotonicTimer); 1432 } 1433 1434 if (NS_IsMainThread()) { 1435 if (mInnerID) { 1436 callData->SetIDs(mOuterID, mInnerID); 1437 } else if (!mPassedInnerID.IsEmpty()) { 1438 callData->SetIDs(u"jsm"_ns, mPassedInnerID); 1439 } else { 1440 nsAutoCString filename; 1441 if (callData->mTopStackFrame.isSome()) { 1442 filename = callData->mTopStackFrame->mFilename; 1443 } 1444 callData->SetIDs(u"jsm"_ns, NS_ConvertUTF8toUTF16(filename)); 1445 } 1446 1447 GetOrCreateMainThreadData()->ProcessCallData(aCx, callData, aData); 1448 1449 // Just because we don't want to expose 1450 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can 1451 // cleanup the mCallDataStorage: 1452 UnstoreCallData(callData); 1453 return; 1454 } 1455 1456 if (WorkletThread::IsOnWorkletThread()) { 1457 RefPtr<ConsoleCallDataWorkletRunnable> runnable = 1458 ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData); 1459 if (!runnable) { 1460 return; 1461 } 1462 1463 NS_DispatchToMainThread(runnable); 1464 return; 1465 } 1466 1467 // We do this only in workers for now. 1468 NotifyHandler(aCx, aData, callData); 1469 1470 if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) { 1471 RefPtr<ConsoleCallDataWorkerRunnable> runnable = 1472 new ConsoleCallDataWorkerRunnable(this, callData); 1473 (void)NS_WARN_IF(!runnable->Dispatch(aCx, aData)); 1474 } 1475 } 1476 1477 MainThreadConsoleData* Console::GetOrCreateMainThreadData() { 1478 AssertIsOnOwningThread(); 1479 1480 if (!mMainThreadData) { 1481 mMainThreadData = new MainThreadConsoleData(); 1482 } 1483 1484 return mMainThreadData; 1485 } 1486 1487 // We store information to lazily compute the stack in the reserved slots of 1488 // LazyStackGetter. The first slot always stores a JS object: it's either the 1489 // JS wrapper of the nsIStackFrame or the actual reified stack representation. 1490 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't 1491 // reified the stack yet, or an UndefinedValue() otherwise. 1492 enum { SLOT_STACKOBJ, SLOT_RAW_STACK }; 1493 1494 bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { 1495 JS::CallArgs args = CallArgsFromVp(aArgc, aVp); 1496 JS::Rooted<JSObject*> callee(aCx, &args.callee()); 1497 1498 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK); 1499 if (v.isUndefined()) { 1500 // Already reified. 1501 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ)); 1502 return true; 1503 } 1504 1505 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate()); 1506 nsTArray<ConsoleStackEntry> reifiedStack; 1507 ReifyStack(aCx, stack, reifiedStack); 1508 1509 JS::Rooted<JS::Value> stackVal(aCx); 1510 if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) { 1511 return false; 1512 } 1513 1514 MOZ_ASSERT(stackVal.isObject()); 1515 1516 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal); 1517 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue()); 1518 1519 args.rval().set(stackVal); 1520 return true; 1521 } 1522 1523 void MainThreadConsoleData::ProcessCallData( 1524 JSContext* aCx, ConsoleCallData* aData, 1525 const Sequence<JS::Value>& aArguments) { 1526 AssertIsOnMainThread(); 1527 MOZ_ASSERT(aData); 1528 aData->mMutex.AssertCurrentThreadOwns(); 1529 1530 JS::Rooted<JS::Value> eventValue(aCx); 1531 1532 // We want to create a console event object and pass it to our 1533 // nsIConsoleAPIStorage implementation. We want to define some accessor 1534 // properties on this object, and those will need to keep an nsIStackFrame 1535 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And 1536 // further, passing untrusted objects to system code is likely to run afoul of 1537 // Object Xrays. So we want to wrap in a system-principal scope here. But 1538 // which one? We could cheat and try to get the underlying JSObject* of 1539 // mStorage, but that's a bit fragile. Instead, we just use the junk scope, 1540 // with explicit permission from the XPConnect module owner. If you're 1541 // tempted to do that anywhere else, talk to said module owner first. 1542 1543 // aCx and aArguments are in the same compartment. 1544 JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope()); 1545 if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope( 1546 aCx, aArguments, targetScope, &eventValue, aData, &mGroupStack))) { 1547 return; 1548 } 1549 1550 if (!mStorage) { 1551 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1"); 1552 } 1553 1554 if (!mStorage) { 1555 NS_WARNING("Failed to get the ConsoleAPIStorage service."); 1556 return; 1557 } 1558 1559 nsAutoString innerID; 1560 1561 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown); 1562 if (aData->mIDType == ConsoleCallData::eString) { 1563 innerID = aData->mInnerIDString; 1564 } else { 1565 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber); 1566 innerID.AppendInt(aData->mInnerIDNumber); 1567 } 1568 1569 if (aData->mMethodName == Console::MethodClear) { 1570 DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID); 1571 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed"); 1572 } 1573 1574 if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) { 1575 NS_WARNING("Failed to record a console event."); 1576 } 1577 } 1578 1579 /* static */ 1580 bool Console::PopulateConsoleNotificationInTheTargetScope( 1581 JSContext* aCx, const Sequence<JS::Value>& aArguments, 1582 JS::Handle<JSObject*> aTargetScope, 1583 JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData, 1584 nsTArray<nsString>* aGroupStack) { 1585 MOZ_ASSERT(aCx); 1586 MOZ_ASSERT(aData); 1587 MOZ_ASSERT(aTargetScope); 1588 MOZ_ASSERT(JS_IsGlobalObject(aTargetScope)); 1589 1590 aData->mMutex.AssertCurrentThreadOwns(); 1591 1592 ConsoleStackEntry frame; 1593 if (aData->mTopStackFrame) { 1594 frame = *aData->mTopStackFrame; 1595 } 1596 1597 ConsoleCommon::ClearException ce(aCx); 1598 RootedDictionary<ConsoleEvent> event(aCx); 1599 1600 event.mAddonId = aData->mAddonId; 1601 1602 event.mID.Construct(); 1603 event.mInnerID.Construct(); 1604 1605 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx); 1606 1607 if (aData->mIDType == ConsoleCallData::eString) { 1608 event.mID.Value().SetAsString() = aData->mOuterIDString; 1609 event.mInnerID.Value().SetAsString() = aData->mInnerIDString; 1610 } else if (aData->mIDType == ConsoleCallData::eNumber) { 1611 event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber; 1612 event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber; 1613 } else { 1614 // aData->mIDType can be eUnknown when we dispatch notifications via 1615 // mConsoleEventNotifier. 1616 event.mID.Value().SetAsUnsignedLongLong() = 0; 1617 event.mInnerID.Value().SetAsUnsignedLongLong() = 0; 1618 } 1619 1620 event.mConsoleID = aData->mConsoleID; 1621 event.mLevel = aData->mMethodString; 1622 event.mFilename = frame.mFilename; 1623 event.mPrefix = aData->mPrefix; 1624 1625 nsCOMPtr<nsIURI> filenameURI; 1626 nsAutoCString pass; 1627 if (NS_IsMainThread() && 1628 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) && 1629 NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) { 1630 nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = 1631 do_QueryInterface(filenameURI); 1632 nsAutoCString spec; 1633 if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) { 1634 event.mFilename = spec; 1635 } 1636 } 1637 1638 event.mSourceId = frame.mSourceId; 1639 event.mLineNumber = frame.mLineNumber; 1640 event.mColumnNumber = frame.mColumnNumber; 1641 event.mFunctionName = frame.mFunctionName; 1642 event.mTimeStamp = aData->mMicroSecondTimeStamp / PR_USEC_PER_MSEC; 1643 event.mMicroSecondTimeStamp = aData->mMicroSecondTimeStamp; 1644 event.mPrivate = aData->mOriginAttributes.IsPrivateBrowsing(); 1645 1646 switch (aData->mMethodName) { 1647 case MethodLog: 1648 case MethodInfo: 1649 case MethodWarn: 1650 case MethodError: 1651 case MethodException: 1652 case MethodDebug: 1653 case MethodAssert: 1654 case MethodGroup: 1655 case MethodGroupCollapsed: 1656 case MethodTrace: 1657 event.mArguments.Construct(); 1658 event.mStyles.Construct(); 1659 if (NS_WARN_IF(!ProcessArguments(aCx, aArguments, 1660 event.mArguments.Value(), 1661 event.mStyles.Value()))) { 1662 return false; 1663 } 1664 1665 break; 1666 1667 default: 1668 event.mArguments.Construct(); 1669 if (NS_WARN_IF( 1670 !event.mArguments.Value().AppendElements(aArguments, fallible))) { 1671 return false; 1672 } 1673 } 1674 1675 if (aData->mMethodName == MethodGroup || 1676 aData->mMethodName == MethodGroupCollapsed) { 1677 ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName, 1678 aGroupStack); 1679 } 1680 1681 else if (aData->mMethodName == MethodGroupEnd) { 1682 if (!UnstoreGroupName(event.mGroupName, aGroupStack)) { 1683 return false; 1684 } 1685 } 1686 1687 else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) { 1688 event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel, 1689 aData->mStartTimerStatus); 1690 } 1691 1692 else if ((aData->mMethodName == MethodTimeEnd || 1693 aData->mMethodName == MethodTimeLog) && 1694 !aArguments.IsEmpty()) { 1695 event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel, 1696 aData->mLogTimerDuration, 1697 aData->mLogTimerStatus); 1698 } 1699 1700 else if (aData->mMethodName == MethodCount || 1701 aData->mMethodName == MethodCountReset) { 1702 event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel, 1703 aData->mCountValue); 1704 } 1705 1706 JSAutoRealm ar2(aCx, aTargetScope); 1707 1708 if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) { 1709 return false; 1710 } 1711 1712 JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject()); 1713 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj, 1714 JSPROP_ENUMERATE))) { 1715 return false; 1716 } 1717 1718 if (ShouldIncludeStackTrace(aData->mMethodName)) { 1719 // Now define the "stacktrace" property on eventObj. There are two cases 1720 // here. Either we came from a worker and have a reified stack, or we want 1721 // to define a getter that will lazily reify the stack. 1722 if (aData->mReifiedStack) { 1723 JS::Rooted<JS::Value> stacktrace(aCx); 1724 if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) || 1725 NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace, 1726 JSPROP_ENUMERATE))) { 1727 return false; 1728 } 1729 } else { 1730 JSFunction* fun = 1731 js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace"); 1732 if (NS_WARN_IF(!fun)) { 1733 return false; 1734 } 1735 1736 JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun)); 1737 1738 // We want to store our stack in the function and have it stay alive. But 1739 // we also need sane access to the C++ nsIStackFrame. So store both a JS 1740 // wrapper and the raw pointer: the former will keep the latter alive. 1741 JS::Rooted<JS::Value> stackVal(aCx); 1742 nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack, &stackVal); 1743 if (NS_WARN_IF(NS_FAILED(rv))) { 1744 return false; 1745 } 1746 1747 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal); 1748 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK, 1749 JS::PrivateValue(aData->mStack.get())); 1750 1751 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", funObj, 1752 nullptr, JSPROP_ENUMERATE))) { 1753 return false; 1754 } 1755 } 1756 } 1757 1758 return true; 1759 } 1760 1761 namespace { 1762 1763 // Helper method for ProcessArguments. Flushes output, if non-empty, to 1764 // aSequence. 1765 bool FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, 1766 nsString& aOutput) { 1767 if (!aOutput.IsEmpty()) { 1768 JS::Rooted<JSString*> str( 1769 aCx, JS_NewUCStringCopyN(aCx, aOutput.get(), aOutput.Length())); 1770 if (NS_WARN_IF(!str)) { 1771 return false; 1772 } 1773 1774 if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) { 1775 return false; 1776 } 1777 1778 aOutput.Truncate(); 1779 } 1780 1781 return true; 1782 } 1783 1784 } // namespace 1785 1786 static void MakeFormatString(nsCString& aFormat, int32_t aInteger, 1787 int32_t aMantissa, char aCh) { 1788 aFormat.Append('%'); 1789 if (aInteger >= 0) { 1790 aFormat.AppendInt(aInteger); 1791 } 1792 1793 if (aMantissa >= 0) { 1794 aFormat.Append('.'); 1795 aFormat.AppendInt(aMantissa); 1796 } 1797 1798 aFormat.Append(aCh); 1799 } 1800 1801 // If the first JS::Value of the array is a string, this method uses it to 1802 // format a string. The supported sequences are: 1803 // %s - string 1804 // %d,%i - integer 1805 // %f - double 1806 // %o,%O - a JS object. 1807 // %c - style string. 1808 // The output is an array where any object is a separated item, the rest is 1809 // unified in a format string. 1810 // Example if the input is: 1811 // "string: %s, integer: %d, object: %o, double: %f", 's', 1, window, 0.9 1812 // The output will be: 1813 // [ "string: s, integer: 1, object: ", window, ", double: 0.9" ] 1814 // 1815 // The aStyles array is populated with the style strings that the function 1816 // finds based the format string. The index of the styles matches the indexes 1817 // of elements that need the custom styling from aSequence. For elements with 1818 // no custom styling the array is padded with null elements. 1819 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData, 1820 Sequence<JS::Value>& aSequence, 1821 Sequence<nsString>& aStyles) { 1822 // This method processes the arguments as format strings (%d, %i, %s...) 1823 // only if the first element of them is a valid and not-empty string. 1824 1825 if (aData.IsEmpty()) { 1826 return true; 1827 } 1828 1829 if (aData.Length() == 1 || !aData[0].isString()) { 1830 return aSequence.AppendElements(aData, fallible); 1831 } 1832 1833 JS::Rooted<JS::Value> format(aCx, aData[0]); 1834 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format)); 1835 if (NS_WARN_IF(!jsString)) { 1836 return false; 1837 } 1838 1839 nsAutoJSString string; 1840 if (NS_WARN_IF(!string.init(aCx, jsString))) { 1841 return false; 1842 } 1843 1844 if (string.IsEmpty()) { 1845 return aSequence.AppendElements(aData, fallible); 1846 } 1847 1848 nsString::const_iterator start, end; 1849 string.BeginReading(start); 1850 string.EndReading(end); 1851 1852 nsString output; 1853 uint32_t index = 1; 1854 1855 while (start != end) { 1856 if (*start != '%') { 1857 output.Append(*start); 1858 ++start; 1859 continue; 1860 } 1861 1862 ++start; 1863 if (start == end) { 1864 output.Append('%'); 1865 break; 1866 } 1867 1868 if (*start == '%') { 1869 output.Append(*start); 1870 ++start; 1871 continue; 1872 } 1873 1874 nsAutoString tmp; 1875 tmp.Append('%'); 1876 1877 int32_t integer = -1; 1878 int32_t mantissa = -1; 1879 1880 // Let's parse %<number>.<number> for %d and %f 1881 if (*start >= '0' && *start <= '9') { 1882 integer = 0; 1883 1884 do { 1885 integer = integer * 10 + *start - '0'; 1886 tmp.Append(*start); 1887 ++start; 1888 } while (*start >= '0' && *start <= '9' && start != end); 1889 } 1890 1891 if (start == end) { 1892 output.Append(tmp); 1893 break; 1894 } 1895 1896 if (*start == '.') { 1897 tmp.Append(*start); 1898 ++start; 1899 1900 if (start == end) { 1901 output.Append(tmp); 1902 break; 1903 } 1904 1905 // '.' must be followed by a number. 1906 if (*start < '0' || *start > '9') { 1907 output.Append(tmp); 1908 continue; 1909 } 1910 1911 mantissa = 0; 1912 1913 do { 1914 mantissa = mantissa * 10 + *start - '0'; 1915 tmp.Append(*start); 1916 ++start; 1917 } while (*start >= '0' && *start <= '9' && start != end); 1918 1919 if (start == end) { 1920 output.Append(tmp); 1921 break; 1922 } 1923 } 1924 1925 char ch = *start; 1926 tmp.Append(ch); 1927 ++start; 1928 1929 switch (ch) { 1930 case 'o': 1931 case 'O': { 1932 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) { 1933 return false; 1934 } 1935 1936 JS::Rooted<JS::Value> v(aCx); 1937 if (index < aData.Length()) { 1938 v = aData[index++]; 1939 } 1940 1941 if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) { 1942 return false; 1943 } 1944 1945 break; 1946 } 1947 1948 case 'c': { 1949 // If there isn't any output but there's already a style, then 1950 // discard the previous style and use the next one instead. 1951 if (output.IsEmpty() && !aStyles.IsEmpty()) { 1952 aStyles.RemoveLastElement(); 1953 } 1954 1955 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) { 1956 return false; 1957 } 1958 1959 if (index < aData.Length()) { 1960 JS::Rooted<JS::Value> v(aCx, aData[index++]); 1961 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v)); 1962 if (NS_WARN_IF(!jsString)) { 1963 return false; 1964 } 1965 1966 int32_t diff = aSequence.Length() - aStyles.Length(); 1967 if (diff > 0) { 1968 for (int32_t i = 0; i < diff; i++) { 1969 if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) { 1970 return false; 1971 } 1972 } 1973 } 1974 1975 nsAutoJSString string; 1976 if (NS_WARN_IF(!string.init(aCx, jsString))) { 1977 return false; 1978 } 1979 1980 if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) { 1981 return false; 1982 } 1983 } 1984 break; 1985 } 1986 1987 case 's': 1988 if (index < aData.Length()) { 1989 JS::Rooted<JS::Value> value(aCx, aData[index++]); 1990 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 1991 if (NS_WARN_IF(!jsString)) { 1992 return false; 1993 } 1994 1995 nsAutoJSString v; 1996 if (NS_WARN_IF(!v.init(aCx, jsString))) { 1997 return false; 1998 } 1999 2000 output.Append(v); 2001 } 2002 break; 2003 2004 case 'd': 2005 case 'i': 2006 if (index < aData.Length()) { 2007 JS::Rooted<JS::Value> value(aCx, aData[index++]); 2008 2009 if (value.isBigInt()) { 2010 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 2011 if (NS_WARN_IF(!jsString)) { 2012 return false; 2013 } 2014 2015 nsAutoJSString v; 2016 if (NS_WARN_IF(!v.init(aCx, jsString))) { 2017 return false; 2018 } 2019 output.Append(v); 2020 break; 2021 } 2022 2023 int32_t v; 2024 if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) { 2025 return false; 2026 } 2027 2028 nsCString format; 2029 MakeFormatString(format, integer, mantissa, 'd'); 2030 output.AppendPrintf(format.get(), v); 2031 } 2032 break; 2033 2034 case 'f': 2035 if (index < aData.Length()) { 2036 JS::Rooted<JS::Value> value(aCx, aData[index++]); 2037 2038 double v; 2039 if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) { 2040 return false; 2041 } 2042 2043 // nspr returns "nan", but we want to expose it as "NaN" 2044 if (std::isnan(v)) { 2045 output.AppendFloat(v); 2046 } else { 2047 nsCString format; 2048 MakeFormatString(format, integer, std::min(mantissa, 15), 'f'); 2049 output.AppendPrintf(format.get(), v); 2050 } 2051 } 2052 break; 2053 2054 default: 2055 output.Append(tmp); 2056 break; 2057 } 2058 } 2059 2060 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) { 2061 return false; 2062 } 2063 2064 // Discard trailing style element if there is no output to apply it to. 2065 if (aStyles.Length() > aSequence.Length()) { 2066 aStyles.TruncateLength(aSequence.Length()); 2067 } 2068 2069 // The rest of the array, if unused by the format string. 2070 for (; index < aData.Length(); ++index) { 2071 if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) { 2072 return false; 2073 } 2074 } 2075 2076 return true; 2077 } 2078 2079 // Stringify and Concat all the JS::Value in a single string using ' ' as 2080 // separator. The new group name will be stored in aGroupStack array. 2081 static void ComposeAndStoreGroupName(JSContext* aCx, 2082 const Sequence<JS::Value>& aData, 2083 nsAString& aName, 2084 nsTArray<nsString>* aGroupStack) { 2085 StringJoinAppend( 2086 aName, u" "_ns, aData, [aCx](nsAString& dest, const JS::Value& valueRef) { 2087 JS::Rooted<JS::Value> value(aCx, valueRef); 2088 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value)); 2089 if (!jsString) { 2090 return; 2091 } 2092 2093 nsAutoJSString string; 2094 if (!string.init(aCx, jsString)) { 2095 return; 2096 } 2097 2098 dest.Append(string); 2099 }); 2100 2101 aGroupStack->AppendElement(aName); 2102 } 2103 2104 // Remove the last group name and return that name. It returns false if 2105 // aGroupStack is empty. 2106 static bool UnstoreGroupName(nsAString& aName, 2107 nsTArray<nsString>* aGroupStack) { 2108 if (aGroupStack->IsEmpty()) { 2109 return false; 2110 } 2111 2112 aName = aGroupStack->PopLastElement(); 2113 return true; 2114 } 2115 2116 Console::TimerStatus Console::StartTimer(JSContext* aCx, const JS::Value& aName, 2117 DOMHighResTimeStamp aTimestamp, 2118 nsAString& aTimerLabel, 2119 DOMHighResTimeStamp* aTimerValue) { 2120 AssertIsOnOwningThread(); 2121 MOZ_ASSERT(aTimerValue); 2122 2123 *aTimerValue = 0; 2124 2125 if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) { 2126 return eTimerMaxReached; 2127 } 2128 2129 JS::Rooted<JS::Value> name(aCx, aName); 2130 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name)); 2131 if (NS_WARN_IF(!jsString)) { 2132 return eTimerJSException; 2133 } 2134 2135 nsAutoJSString label; 2136 if (NS_WARN_IF(!label.init(aCx, jsString))) { 2137 return eTimerJSException; 2138 } 2139 2140 aTimerLabel = label; 2141 2142 if (mTimerRegistry.WithEntryHandle(label, [&](auto&& entry) { 2143 if (entry) { 2144 return true; 2145 } 2146 entry.Insert(aTimestamp); 2147 return false; 2148 })) { 2149 return eTimerAlreadyExists; 2150 } 2151 2152 *aTimerValue = aTimestamp; 2153 return eTimerDone; 2154 } 2155 2156 /* static */ 2157 JS::Value Console::CreateStartTimerValue(JSContext* aCx, 2158 const nsAString& aTimerLabel, 2159 TimerStatus aTimerStatus) { 2160 MOZ_ASSERT(aTimerStatus != eTimerUnknown); 2161 2162 if (aTimerStatus != eTimerDone) { 2163 return CreateTimerError(aCx, aTimerLabel, aTimerStatus); 2164 } 2165 2166 RootedDictionary<ConsoleTimerStart> timer(aCx); 2167 2168 timer.mName = aTimerLabel; 2169 2170 JS::Rooted<JS::Value> value(aCx); 2171 if (!ToJSValue(aCx, timer, &value)) { 2172 return JS::UndefinedValue(); 2173 } 2174 2175 return value; 2176 } 2177 2178 Console::TimerStatus Console::LogTimer(JSContext* aCx, const JS::Value& aName, 2179 DOMHighResTimeStamp aTimestamp, 2180 nsAString& aTimerLabel, 2181 double* aTimerDuration, 2182 bool aCancelTimer) { 2183 AssertIsOnOwningThread(); 2184 MOZ_ASSERT(aTimerDuration); 2185 2186 *aTimerDuration = 0; 2187 2188 JS::Rooted<JS::Value> name(aCx, aName); 2189 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name)); 2190 if (NS_WARN_IF(!jsString)) { 2191 return eTimerJSException; 2192 } 2193 2194 nsAutoJSString key; 2195 if (NS_WARN_IF(!key.init(aCx, jsString))) { 2196 return eTimerJSException; 2197 } 2198 2199 aTimerLabel = key; 2200 2201 DOMHighResTimeStamp value = 0; 2202 2203 if (aCancelTimer) { 2204 if (!mTimerRegistry.Remove(key, &value)) { 2205 NS_WARNING("mTimerRegistry entry not found"); 2206 return eTimerDoesntExist; 2207 } 2208 } else { 2209 if (!mTimerRegistry.Get(key, &value)) { 2210 NS_WARNING("mTimerRegistry entry not found"); 2211 return eTimerDoesntExist; 2212 } 2213 } 2214 2215 *aTimerDuration = aTimestamp - value; 2216 return eTimerDone; 2217 } 2218 2219 /* static */ 2220 JS::Value Console::CreateLogOrEndTimerValue(JSContext* aCx, 2221 const nsAString& aLabel, 2222 double aDuration, 2223 TimerStatus aStatus) { 2224 if (aStatus != eTimerDone) { 2225 return CreateTimerError(aCx, aLabel, aStatus); 2226 } 2227 2228 RootedDictionary<ConsoleTimerLogOrEnd> timer(aCx); 2229 timer.mName = aLabel; 2230 timer.mDuration = aDuration; 2231 2232 JS::Rooted<JS::Value> value(aCx); 2233 if (!ToJSValue(aCx, timer, &value)) { 2234 return JS::UndefinedValue(); 2235 } 2236 2237 return value; 2238 } 2239 2240 /* static */ 2241 JS::Value Console::CreateTimerError(JSContext* aCx, const nsAString& aLabel, 2242 TimerStatus aStatus) { 2243 MOZ_ASSERT(aStatus != eTimerUnknown && aStatus != eTimerDone); 2244 2245 RootedDictionary<ConsoleTimerError> error(aCx); 2246 2247 error.mName = aLabel; 2248 2249 switch (aStatus) { 2250 case eTimerAlreadyExists: 2251 error.mError.AssignLiteral("timerAlreadyExists"); 2252 break; 2253 2254 case eTimerDoesntExist: 2255 error.mError.AssignLiteral("timerDoesntExist"); 2256 break; 2257 2258 case eTimerJSException: 2259 error.mError.AssignLiteral("timerJSError"); 2260 break; 2261 2262 case eTimerMaxReached: 2263 error.mError.AssignLiteral("maxTimersExceeded"); 2264 break; 2265 2266 default: 2267 MOZ_CRASH("Unsupported status"); 2268 break; 2269 } 2270 2271 JS::Rooted<JS::Value> value(aCx); 2272 if (!ToJSValue(aCx, error, &value)) { 2273 return JS::UndefinedValue(); 2274 } 2275 2276 return value; 2277 } 2278 2279 uint32_t Console::IncreaseCounter(JSContext* aCx, 2280 const Sequence<JS::Value>& aArguments, 2281 nsAString& aCountLabel) { 2282 AssertIsOnOwningThread(); 2283 2284 ConsoleCommon::ClearException ce(aCx); 2285 2286 MOZ_ASSERT(!aArguments.IsEmpty()); 2287 2288 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]); 2289 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue)); 2290 if (!jsString) { 2291 return 0; // We cannot continue. 2292 } 2293 2294 nsAutoJSString string; 2295 if (!string.init(aCx, jsString)) { 2296 return 0; // We cannot continue. 2297 } 2298 2299 aCountLabel = string; 2300 2301 const bool maxCountersReached = mCounterRegistry.Count() >= MAX_PAGE_COUNTERS; 2302 return mCounterRegistry.WithEntryHandle( 2303 aCountLabel, [maxCountersReached](auto&& entry) -> uint32_t { 2304 if (entry) { 2305 ++entry.Data(); 2306 } else { 2307 if (maxCountersReached) { 2308 return MAX_PAGE_COUNTERS; 2309 } 2310 entry.Insert(1); 2311 } 2312 return entry.Data(); 2313 }); 2314 } 2315 2316 uint32_t Console::ResetCounter(JSContext* aCx, 2317 const Sequence<JS::Value>& aArguments, 2318 nsAString& aCountLabel) { 2319 AssertIsOnOwningThread(); 2320 2321 ConsoleCommon::ClearException ce(aCx); 2322 2323 MOZ_ASSERT(!aArguments.IsEmpty()); 2324 2325 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]); 2326 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue)); 2327 if (!jsString) { 2328 return 0; // We cannot continue. 2329 } 2330 2331 nsAutoJSString string; 2332 if (!string.init(aCx, jsString)) { 2333 return 0; // We cannot continue. 2334 } 2335 2336 aCountLabel = string; 2337 2338 if (mCounterRegistry.Remove(aCountLabel)) { 2339 return 0; 2340 } 2341 2342 // Let's return something different than 0 if the key doesn't exist. 2343 return MAX_PAGE_COUNTERS; 2344 } 2345 2346 // This method generates a ConsoleCounter dictionary as JS::Value. If 2347 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError 2348 // instead. See IncreaseCounter. 2349 // * aCx - this is the context that will root the returned value. 2350 // * aCountLabel - this label must be what IncreaseCounter received as 2351 // aTimerLabel. 2352 // * aCountValue - the return value of IncreaseCounter. 2353 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx, 2354 const nsAString& aCountLabel, 2355 uint32_t aCountValue) { 2356 ConsoleCommon::ClearException ce(aCx); 2357 2358 if (aCountValue == MAX_PAGE_COUNTERS) { 2359 RootedDictionary<ConsoleCounterError> error(aCx); 2360 error.mLabel = aCountLabel; 2361 error.mError.AssignLiteral("counterDoesntExist"); 2362 2363 JS::Rooted<JS::Value> value(aCx); 2364 if (!ToJSValue(aCx, error, &value)) { 2365 return JS::UndefinedValue(); 2366 } 2367 2368 return value; 2369 } 2370 2371 RootedDictionary<ConsoleCounter> data(aCx); 2372 data.mLabel = aCountLabel; 2373 data.mCount = aCountValue; 2374 2375 JS::Rooted<JS::Value> value(aCx); 2376 if (!ToJSValue(aCx, data, &value)) { 2377 return JS::UndefinedValue(); 2378 } 2379 2380 return value; 2381 } 2382 2383 /* static */ 2384 bool Console::ShouldIncludeStackTrace(MethodName aMethodName) { 2385 switch (aMethodName) { 2386 case MethodError: 2387 case MethodException: 2388 case MethodAssert: 2389 case MethodTrace: 2390 return true; 2391 default: 2392 return false; 2393 } 2394 } 2395 2396 JSObject* MainThreadConsoleData::GetOrCreateSandbox(JSContext* aCx, 2397 nsIPrincipal* aPrincipal) { 2398 AssertIsOnMainThread(); 2399 2400 if (!mSandbox) { 2401 nsIXPConnect* xpc = nsContentUtils::XPConnect(); 2402 MOZ_ASSERT(xpc, "This should never be null!"); 2403 2404 JS::Rooted<JSObject*> sandbox(aCx); 2405 nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address()); 2406 if (NS_WARN_IF(NS_FAILED(rv))) { 2407 return nullptr; 2408 } 2409 2410 mSandbox = new JSObjectHolder(aCx, sandbox); 2411 } 2412 2413 return mSandbox->GetJSObject(); 2414 } 2415 2416 bool Console::StoreCallData(JSContext* aCx, ConsoleCallData* aCallData, 2417 const Sequence<JS::Value>& aArguments) { 2418 AssertIsOnOwningThread(); 2419 2420 if (NS_WARN_IF(!mArgumentStorage.growBy(1))) { 2421 return false; 2422 } 2423 if (!mArgumentStorage.end()[-1].Initialize(aCx, aArguments)) { 2424 mArgumentStorage.shrinkBy(1); 2425 return false; 2426 } 2427 2428 MOZ_ASSERT(aCallData); 2429 MOZ_ASSERT(!mCallDataStorage.Contains(aCallData)); 2430 2431 mCallDataStorage.AppendElement(aCallData); 2432 2433 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length()); 2434 2435 if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) { 2436 mCallDataStorage.RemoveElementAt(0); 2437 mArgumentStorage.erase(&mArgumentStorage[0]); 2438 } 2439 return true; 2440 } 2441 2442 void Console::UnstoreCallData(ConsoleCallData* aCallData) { 2443 AssertIsOnOwningThread(); 2444 2445 MOZ_ASSERT(aCallData); 2446 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length()); 2447 2448 size_t index = mCallDataStorage.IndexOf(aCallData); 2449 // It can be that mCallDataStorage has been already cleaned in case the 2450 // processing of the argument of some Console methods triggers the 2451 // window.close(). 2452 if (index == mCallDataStorage.NoIndex) { 2453 return; 2454 } 2455 2456 mCallDataStorage.RemoveElementAt(index); 2457 mArgumentStorage.erase(&mArgumentStorage[index]); 2458 } 2459 2460 void Console::NotifyHandler(JSContext* aCx, 2461 const Sequence<JS::Value>& aArguments, 2462 ConsoleCallData* aCallData) { 2463 AssertIsOnOwningThread(); 2464 MOZ_ASSERT(!NS_IsMainThread()); 2465 MOZ_ASSERT(aCallData); 2466 2467 if (!mConsoleEventNotifier) { 2468 return; 2469 } 2470 2471 JS::Rooted<JS::Value> value(aCx); 2472 2473 JS::Rooted<JSObject*> callableGlobal( 2474 aCx, mConsoleEventNotifier->CallbackGlobalOrNull()); 2475 if (NS_WARN_IF(!callableGlobal)) { 2476 return; 2477 } 2478 2479 // aCx and aArguments are in the same compartment because this method is 2480 // called directly when a Console.something() runs. 2481 // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be 2482 // sent to. 2483 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope( 2484 aCx, aArguments, callableGlobal, &value, aCallData, &mGroupStack))) { 2485 return; 2486 } 2487 2488 JS::Rooted<JS::Value> ignored(aCx); 2489 RefPtr<AnyCallback> notifier(mConsoleEventNotifier); 2490 notifier->Call(value, &ignored); 2491 } 2492 2493 void Console::RetrieveConsoleEvents(JSContext* aCx, 2494 nsTArray<JS::Value>& aEvents, 2495 ErrorResult& aRv) { 2496 AssertIsOnOwningThread(); 2497 2498 // We don't want to expose this functionality to main-thread yet. 2499 MOZ_ASSERT(!NS_IsMainThread()); 2500 2501 JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx)); 2502 2503 for (uint32_t i = 0; i < mArgumentStorage.length(); ++i) { 2504 JS::Rooted<JS::Value> value(aCx); 2505 2506 JS::Rooted<JSObject*> sequenceScope(aCx, mArgumentStorage[i].Global()); 2507 JSAutoRealm ar(aCx, sequenceScope); 2508 2509 Sequence<JS::Value> sequence; 2510 SequenceRooter<JS::Value> arguments(aCx, &sequence); 2511 2512 if (!mArgumentStorage[i].PopulateArgumentsSequence(sequence)) { 2513 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 2514 return; 2515 } 2516 2517 // Here we have aCx and sequence in the same compartment. 2518 // targetScope is the destination scope and value will be populated in its 2519 // compartment. 2520 { 2521 MutexAutoLock lock(mCallDataStorage[i]->mMutex); 2522 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope( 2523 aCx, sequence, targetScope, &value, mCallDataStorage[i], 2524 &mGroupStack))) { 2525 aRv.Throw(NS_ERROR_FAILURE); 2526 return; 2527 } 2528 } 2529 2530 aEvents.AppendElement(value); 2531 } 2532 } 2533 2534 void Console::SetConsoleEventHandler(AnyCallback* aHandler) { 2535 AssertIsOnOwningThread(); 2536 2537 // We don't want to expose this functionality to main-thread yet. 2538 MOZ_ASSERT(!NS_IsMainThread()); 2539 2540 mConsoleEventNotifier = aHandler; 2541 } 2542 2543 void Console::AssertIsOnOwningThread() const { 2544 NS_ASSERT_OWNINGTHREAD(Console); 2545 } 2546 2547 bool Console::IsShuttingDown() const { 2548 MOZ_ASSERT(mStatus != eUnknown); 2549 return mStatus == eShuttingDown; 2550 } 2551 2552 /* static */ 2553 already_AddRefed<Console> Console::GetConsole(const GlobalObject& aGlobal) { 2554 ErrorResult rv; 2555 RefPtr<Console> console = GetConsoleInternal(aGlobal, rv); 2556 if (NS_WARN_IF(rv.Failed()) || !console) { 2557 rv.SuppressException(); 2558 return nullptr; 2559 } 2560 2561 console->AssertIsOnOwningThread(); 2562 2563 if (console->IsShuttingDown()) { 2564 return nullptr; 2565 } 2566 2567 return console.forget(); 2568 } 2569 2570 /* static */ 2571 already_AddRefed<Console> Console::GetConsoleInternal( 2572 const GlobalObject& aGlobal, ErrorResult& aRv) { 2573 // Window 2574 if (NS_IsMainThread()) { 2575 nsCOMPtr<nsPIDOMWindowInner> innerWindow = 2576 do_QueryInterface(aGlobal.GetAsSupports()); 2577 2578 // we are probably running a chrome script. 2579 if (!innerWindow) { 2580 RefPtr<Console> console = new Console(aGlobal.Context(), nullptr, 0, 0); 2581 console->Initialize(aRv); 2582 if (NS_WARN_IF(aRv.Failed())) { 2583 return nullptr; 2584 } 2585 2586 return console.forget(); 2587 } 2588 2589 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow); 2590 return window->GetConsole(aGlobal.Context(), aRv); 2591 } 2592 2593 // Worklet 2594 nsCOMPtr<WorkletGlobalScope> workletScope = 2595 do_QueryInterface(aGlobal.GetAsSupports()); 2596 if (workletScope) { 2597 WorkletThread::AssertIsOnWorkletThread(); 2598 return workletScope->GetConsole(aGlobal.Context(), aRv); 2599 } 2600 2601 // Workers 2602 MOZ_ASSERT(!NS_IsMainThread()); 2603 2604 JSContext* cx = aGlobal.Context(); 2605 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); 2606 MOZ_ASSERT(workerPrivate); 2607 2608 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 2609 if (NS_WARN_IF(!global)) { 2610 return nullptr; 2611 } 2612 2613 WorkerGlobalScope* scope = workerPrivate->GlobalScope(); 2614 MOZ_ASSERT(scope); 2615 2616 // Normal worker scope. 2617 if (scope == global) { 2618 return scope->GetConsole(aRv); 2619 } 2620 2621 // Debugger worker scope 2622 2623 WorkerDebuggerGlobalScope* debuggerScope = 2624 workerPrivate->DebuggerGlobalScope(); 2625 MOZ_ASSERT(debuggerScope); 2626 MOZ_ASSERT(debuggerScope == global, "Which kind of global do we have?"); 2627 2628 return debuggerScope->GetConsole(aRv); 2629 } 2630 2631 bool Console::MonotonicTimer(JSContext* aCx, MethodName aMethodName, 2632 const Sequence<JS::Value>& aData, 2633 DOMHighResTimeStamp* aTimeStamp) { 2634 if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(mGlobal)) { 2635 nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(innerWindow); 2636 MOZ_ASSERT(win); 2637 2638 RefPtr<Performance> performance = win->GetPerformance(); 2639 if (!performance) { 2640 return false; 2641 } 2642 2643 *aTimeStamp = performance->Now(); 2644 return true; 2645 } 2646 2647 if (NS_IsMainThread()) { 2648 *aTimeStamp = (TimeStamp::Now() - mCreationTimeStamp).ToMilliseconds(); 2649 return true; 2650 } 2651 2652 if (nsCOMPtr<WorkletGlobalScope> workletGlobal = do_QueryInterface(mGlobal)) { 2653 *aTimeStamp = workletGlobal->TimeStampToDOMHighRes(TimeStamp::Now()); 2654 return true; 2655 } 2656 2657 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 2658 MOZ_ASSERT(workerPrivate); 2659 2660 *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now()); 2661 return true; 2662 } 2663 2664 /* static */ 2665 already_AddRefed<ConsoleInstance> Console::CreateInstance( 2666 const GlobalObject& aGlobal, const ConsoleInstanceOptions& aOptions) { 2667 RefPtr<ConsoleInstance> console = 2668 new ConsoleInstance(aGlobal.Context(), aOptions); 2669 return console.forget(); 2670 } 2671 2672 void Console::StringifyElement(Element* aElement, nsAString& aOut) { 2673 aOut.AppendLiteral("<"); 2674 aOut.Append(aElement->LocalName()); 2675 uint32_t attrCount = aElement->GetAttrCount(); 2676 nsAutoString idAttr; 2677 nsAutoString classAttr; 2678 nsAutoString nameAttr; 2679 nsAutoString otherAttrs; 2680 for (uint32_t i = 0; i < attrCount; i++) { 2681 BorrowedAttrInfo attrInfo = aElement->GetAttrInfoAt(i); 2682 nsAutoString attrValue; 2683 attrInfo.mValue->ToString(attrValue); 2684 2685 const nsAttrName* attrName = attrInfo.mName; 2686 if (attrName->Equals(nsGkAtoms::id)) { 2687 idAttr.AppendLiteral(" id=\""); 2688 idAttr.Append(attrValue); 2689 idAttr.AppendLiteral("\""); 2690 } else if (attrName->Equals(nsGkAtoms::_class)) { 2691 classAttr.AppendLiteral(" class=\""); 2692 classAttr.Append(attrValue); 2693 classAttr.AppendLiteral("\""); 2694 } else if (attrName->Equals(nsGkAtoms::name)) { 2695 nameAttr.AppendLiteral(" name=\""); 2696 nameAttr.Append(attrValue); 2697 nameAttr.AppendLiteral("\""); 2698 } else { 2699 nsAutoString attrNameStr; 2700 attrName->GetQualifiedName(attrNameStr); 2701 otherAttrs.AppendLiteral(" "); 2702 otherAttrs.Append(attrNameStr); 2703 otherAttrs.AppendLiteral("=\""); 2704 otherAttrs.Append(attrValue); 2705 otherAttrs.AppendLiteral("\""); 2706 } 2707 } 2708 if (!idAttr.IsEmpty()) { 2709 aOut.Append(idAttr); 2710 } 2711 if (!classAttr.IsEmpty()) { 2712 aOut.Append(classAttr); 2713 } 2714 if (!nameAttr.IsEmpty()) { 2715 aOut.Append(nameAttr); 2716 } 2717 if (!otherAttrs.IsEmpty()) { 2718 aOut.Append(otherAttrs); 2719 } 2720 aOut.AppendLiteral(">"); 2721 } 2722 2723 void Console::MaybeExecuteDumpFunction(JSContext* aCx, MethodName aMethodName, 2724 const nsAString& aMethodString, 2725 const Sequence<JS::Value>& aData, 2726 nsIStackFrame* aStack, 2727 DOMHighResTimeStamp aMonotonicTimer) { 2728 if (mLogModule->ShouldLog(InternalLogLevelToMozLog(aMethodName))) { 2729 nsString message = GetDumpMessage(aCx, aMethodName, aMethodString, aData, 2730 aStack, aMonotonicTimer, true); 2731 2732 MOZ_LOG(mLogModule, InternalLogLevelToMozLog(aMethodName), 2733 ("%s", NS_ConvertUTF16toUTF8(message).get())); 2734 } 2735 2736 if (!mDumpFunction && !mDumpToStdout) { 2737 return; 2738 } 2739 nsString message = GetDumpMessage(aCx, aMethodName, aMethodString, aData, 2740 aStack, aMonotonicTimer, false); 2741 2742 ExecuteDumpFunction(message); 2743 } 2744 2745 nsString Console::GetDumpMessage(JSContext* aCx, MethodName aMethodName, 2746 const nsAString& aMethodString, 2747 const Sequence<JS::Value>& aData, 2748 nsIStackFrame* aStack, 2749 DOMHighResTimeStamp aMonotonicTimer, 2750 bool aIsForMozLog) { 2751 nsString message; 2752 // MOZ_LOG already logs either console or the prefix 2753 if (!aIsForMozLog) { 2754 message.AssignLiteral("console."); 2755 } else { 2756 message.AssignLiteral(""); 2757 } 2758 message.Append(aMethodString); 2759 message.AppendLiteral(": "); 2760 2761 if (!aIsForMozLog && !mPrefix.IsEmpty()) { 2762 message.Append(mPrefix); 2763 message.AppendLiteral(": "); 2764 } 2765 2766 for (uint32_t i = 0; i < aData.Length(); ++i) { 2767 JS::Rooted<JS::Value> v(aCx, aData[i]); 2768 if (v.isObject()) { 2769 Element* element = nullptr; 2770 if (NS_SUCCEEDED(UNWRAP_OBJECT(Element, &v, element))) { 2771 if (i != 0) { 2772 message.AppendLiteral(" "); 2773 } 2774 StringifyElement(element, message); 2775 continue; 2776 } 2777 } 2778 2779 JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v)); 2780 if (!jsString) { 2781 continue; 2782 } 2783 2784 nsAutoJSString string; 2785 if (NS_WARN_IF(!string.init(aCx, jsString))) { 2786 return message; 2787 } 2788 2789 if (i != 0) { 2790 message.AppendLiteral(" "); 2791 } 2792 2793 if (string.EqualsLiteral("({})")) { 2794 // ({}) is a generic serialization, possibly from XPC_WN_Shared_ToSource. 2795 // Such a serialization is rather unhelpful, so try .toString() instead. 2796 // When the input is a Components.Exception instance, the result will 2797 // contain the error message, code and stack, which eases debugging. 2798 JS::Rooted<JSString*> jsString2(aCx, JS::ToString(aCx, v)); 2799 nsAutoJSString string2; 2800 if (jsString2 && string2.init(aCx, jsString2)) { 2801 message.Append(string2); 2802 continue; 2803 } 2804 } 2805 message.Append(string); 2806 } 2807 2808 if (aMethodName == MethodTime || aMethodName == MethodTimeEnd) { 2809 message.AppendLiteral(" @ "); 2810 message.AppendFloat(aMonotonicTimer); 2811 } 2812 2813 message.AppendLiteral("\n"); 2814 2815 // aStack can be null. 2816 2817 nsCOMPtr<nsIStackFrame> stack(aStack); 2818 2819 while (stack) { 2820 nsAutoCString filename; 2821 stack->GetFilename(aCx, filename); 2822 2823 AppendUTF8toUTF16(filename, message); 2824 message.AppendLiteral(" "); 2825 2826 message.AppendInt(stack->GetLineNumber(aCx)); 2827 message.AppendLiteral(" "); 2828 2829 nsAutoString functionName; 2830 stack->GetName(aCx, functionName); 2831 2832 message.Append(functionName); 2833 message.AppendLiteral("\n"); 2834 2835 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx); 2836 2837 if (!caller) { 2838 caller = stack->GetAsyncCaller(aCx); 2839 } 2840 2841 stack.swap(caller); 2842 } 2843 2844 return message; 2845 } 2846 2847 void Console::ExecuteDumpFunction(const nsAString& aMessage) { 2848 if (mDumpFunction) { 2849 RefPtr<ConsoleInstanceDumpCallback> dumpFunction(mDumpFunction); 2850 dumpFunction->Call(aMessage); 2851 return; 2852 } 2853 2854 NS_ConvertUTF16toUTF8 str(aMessage); 2855 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get())); 2856 #ifdef ANDROID 2857 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get()); 2858 #endif 2859 fputs(str.get(), stdout); 2860 fflush(stdout); 2861 } 2862 2863 bool Console::ShouldProceed(MethodName aName) const { 2864 return mCurrentLogLevel <= InternalLogLevelToInteger(aName); 2865 } 2866 2867 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const { 2868 switch (aLevel) { 2869 case ConsoleLogLevel::All: 2870 return 0; 2871 case ConsoleLogLevel::Debug: 2872 return 2; 2873 case ConsoleLogLevel::Log: 2874 return 3; 2875 case ConsoleLogLevel::Info: 2876 return 3; 2877 case ConsoleLogLevel::Clear: 2878 return 3; 2879 case ConsoleLogLevel::Trace: 2880 return 3; 2881 case ConsoleLogLevel::TimeLog: 2882 return 3; 2883 case ConsoleLogLevel::TimeEnd: 2884 return 3; 2885 case ConsoleLogLevel::Time: 2886 return 3; 2887 case ConsoleLogLevel::Group: 2888 return 3; 2889 case ConsoleLogLevel::GroupEnd: 2890 return 3; 2891 case ConsoleLogLevel::Profile: 2892 return 3; 2893 case ConsoleLogLevel::ProfileEnd: 2894 return 3; 2895 case ConsoleLogLevel::Dir: 2896 return 3; 2897 case ConsoleLogLevel::Dirxml: 2898 return 3; 2899 case ConsoleLogLevel::Warn: 2900 return 4; 2901 case ConsoleLogLevel::Error: 2902 return 5; 2903 case ConsoleLogLevel::Off: 2904 return UINT32_MAX; 2905 default: 2906 MOZ_CRASH( 2907 "ConsoleLogLevel is out of sync with the Console implementation!"); 2908 return 0; 2909 } 2910 } 2911 2912 uint32_t Console::InternalLogLevelToInteger(MethodName aName) const { 2913 switch (aName) { 2914 case MethodLog: 2915 return 3; 2916 case MethodInfo: 2917 return 3; 2918 case MethodWarn: 2919 return 4; 2920 case MethodError: 2921 return 5; 2922 case MethodException: 2923 return 5; 2924 case MethodDebug: 2925 return 2; 2926 case MethodTable: 2927 return 3; 2928 case MethodTrace: 2929 return 3; 2930 case MethodDir: 2931 return 3; 2932 case MethodDirxml: 2933 return 3; 2934 case MethodGroup: 2935 return 3; 2936 case MethodGroupCollapsed: 2937 return 3; 2938 case MethodGroupEnd: 2939 return 3; 2940 case MethodTime: 2941 return 3; 2942 case MethodTimeLog: 2943 return 3; 2944 case MethodTimeEnd: 2945 return 3; 2946 case MethodTimeStamp: 2947 return 3; 2948 case MethodAssert: 2949 return 3; 2950 case MethodCount: 2951 return 3; 2952 case MethodCountReset: 2953 return 3; 2954 case MethodClear: 2955 return 3; 2956 case MethodProfile: 2957 return 3; 2958 case MethodProfileEnd: 2959 return 3; 2960 default: 2961 MOZ_CRASH("MethodName is out of sync with the Console implementation!"); 2962 return 0; 2963 } 2964 } 2965 2966 LogLevel Console::InternalLogLevelToMozLog(MethodName aName) const { 2967 switch (aName) { 2968 case MethodLog: 2969 return LogLevel::Info; 2970 case MethodInfo: 2971 return LogLevel::Info; 2972 case MethodWarn: 2973 return LogLevel::Warning; 2974 case MethodError: 2975 return LogLevel::Error; 2976 case MethodException: 2977 return LogLevel::Error; 2978 case MethodDebug: 2979 return LogLevel::Debug; 2980 case MethodTable: 2981 return LogLevel::Info; 2982 case MethodTrace: 2983 return LogLevel::Info; 2984 case MethodDir: 2985 return LogLevel::Info; 2986 case MethodDirxml: 2987 return LogLevel::Info; 2988 case MethodGroup: 2989 return LogLevel::Info; 2990 case MethodGroupCollapsed: 2991 return LogLevel::Info; 2992 case MethodGroupEnd: 2993 return LogLevel::Info; 2994 case MethodTime: 2995 return LogLevel::Info; 2996 case MethodTimeLog: 2997 return LogLevel::Info; 2998 case MethodTimeEnd: 2999 return LogLevel::Info; 3000 case MethodTimeStamp: 3001 return LogLevel::Info; 3002 case MethodAssert: 3003 return LogLevel::Error; 3004 case MethodCount: 3005 return LogLevel::Info; 3006 case MethodCountReset: 3007 return LogLevel::Info; 3008 case MethodClear: 3009 return LogLevel::Info; 3010 case MethodProfile: 3011 return LogLevel::Info; 3012 case MethodProfileEnd: 3013 return LogLevel::Info; 3014 default: 3015 MOZ_CRASH("MethodName is out of sync with the Console implementation!"); 3016 return LogLevel::Disabled; 3017 } 3018 } 3019 3020 bool Console::ArgumentData::Initialize(JSContext* aCx, 3021 const Sequence<JS::Value>& aArguments) { 3022 mGlobal = JS::CurrentGlobalOrNull(aCx); 3023 3024 if (NS_WARN_IF(!mArguments.AppendElements(aArguments, fallible))) { 3025 return false; 3026 } 3027 3028 return true; 3029 } 3030 3031 void Console::ArgumentData::Trace(const TraceCallbacks& aCallbacks, 3032 void* aClosure) { 3033 ArgumentData* tmp = this; 3034 for (uint32_t i = 0; i < mArguments.Length(); ++i) { 3035 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments[i]) 3036 } 3037 3038 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) 3039 } 3040 3041 bool Console::ArgumentData::PopulateArgumentsSequence( 3042 Sequence<JS::Value>& aSequence) const { 3043 AssertIsOnOwningThread(); 3044 3045 for (uint32_t i = 0; i < mArguments.Length(); ++i) { 3046 if (NS_WARN_IF(!aSequence.AppendElement(mArguments[i], fallible))) { 3047 return false; 3048 } 3049 } 3050 3051 return true; 3052 } 3053 3054 } // namespace mozilla::dom