Files
palemoon27/dom/base/Console.cpp
T
roytam1 faafb5fd9d import changes from `dev' branch of rmottola/Arctic-Fox:
- As suggested in PR 101, use OpenBSD assembler files. Update the NetBSD on them and use .S instead of .s, to indicate files to process (or preprocessor would fail on comments). (6a17dbacc3)
- Bug 1229769 - Expose Promise interface to WorkerDebugger #ifdef SPIDERMONKEY_PROMISE;r=bz (da9e838c23)
- Bug 1155969 - Make xpt.py flake8 compliant. r=ted (84f8eab5a3)
- Bug 977464 - Always relink XPT files for all changed XPIDL interfaces without requiring the IID to be revved; r=khuey (9b22512c41)
- Bug 977464 follow-up: Fix the indentation to use 4 spaces (bd68a8ebc3)
- Bug 1240053 - Consider the order of methods, their params, and constant important when comparing XPT interfaces to decide whether to relink XPT files; r=khuey (b9253dd183)
- Bug 1264377. Get rid of some unnecessary custom JSClass hook functions in xpconnect sandboxes and DOM simple globals. r=bholley (60950b416b)
- Bug 1258496 - Purge message manager cached scripts on 'message-manager-flush-caches' notification. r=smaug (028b229d02)
- Bug 1251298 - Null out |*idp| when necessary in DoInterfaceDescriptor. r=khuey. (dbdd15dae8)
- Bug 659625 - part1: implement Console::clear in dom/base/Console.cpp;r=baku (17c4b33789)
- Bug 659625 - part2: implement console.clear in devtools webconsole;r=bgrins (b72c6173ee)
- Bug 1248507 - p5. DecoderDoctorDiagnostics implementation - r=jya,bz (22f68130af)
- Bug 1248507 - p6. Minimal notification definition - r=bz (02f3eeb2f9)
- Bug 1248507 - p7. Notify decoder-doctor-notification listeners - r=jya,bz (2c2eb33388)
- Bug 1248507 - p8. FFMpeg checks: Console message - r=bz (50a993c143)
- Bug 1248507 - p9. FFMpeg checks: Notification definition - r=bz (0bcdcc090c)
- Bug 1248507 - p10. Detect and report when FFMpeg/Linux fails to load - r=jya (28137efda0)
- Bug 1190939: Decode VP9 4:4:4 properly. r=jya (98508bb48b)
- Bug 1232911 - [1.2] Allow to test for specific VPX MIME type version. r=cpearce (1b53e02981)
- Bug 1251887 - Add break to unintentional switch fallthrough in GfxInfoBase.cpp to fix -Wimplicit-fallthrough warning. r=milan (9969a7bec7)
- Bug 1232911 - [2.2] Add VPX decoding blocking support. r=snorp (fa860a9d4d)
- Bug 1249777: Added support for 10.11 in the blocklisting code as well. r=mstange (479f629083)
- Bug 1242084 - Fix GfxInfoBase nsStringBuffer leak. r=dvander (87b38ee72d)
- Bug 1222201: Only use container calculated dimensions. r=cpearce (693ebdf450)
- Bug 1190240 - Cannot compile WMFVideoMFTManager.cpp using Windows 10 SDK. r=cpearce (8ee2e315f5)
- Bug 1248496 - Enable D3D11 DXVA. r=ajones (a79df0baf2)
- Bug 1248496 - Show which DXVA API is being used in about:support. r=jya (1f6b1f0c8e)
- Bug 1257028 - Fallback to d3d9 decoding if d3d11 fails. r=cpearce (5ad7c159f1)
- Bug 1232045 - WebMDemuxer handles resolution changes. r=jya (18bdc79b1c)
- Bug 1243538: P1. Make MediaInfo::mImage an nsIntSize again and introduce a mImageRect member. r=mattwoodrow (a446cca01e)
- Bug 1243538: P2. Add convenience VideoInfo::ScaledImageRect. r=mattwoodrow (657e675b72)
- Bug 1243538: P3. Adjust libvpx decoder to allow different decoding size from metadata. r=mattwoodrow (50949ce02d)
- Bug 1243538: P4. Adjust ffvpx decoder to allow different decoding size from metadata. r=mattwoodrow (392c8939f5)
- Bug 1243538: P5. Adjust wmf decoder to allow different decoding size from metadata. r=cpearce (f50940564f)
- Bug 1239611 - Remove GonkNativeWindowClient r=nical (2c7ccb54a4)
- Bug 1170589 - Force decoder to use all allocated buffers. r=bwu (7e5c02e48a)
- Bug 1222923 - Enable MOZ_FMP4 on gonk L r=jolin (c04ad6ff55)
- Bug 1178214 - Return INIT_ERROR when video resolution exceeds hw codec capability. r=sotaro (bf3c45cde1)
- Bug 1147304 - Send codec specific data for MPEG4 codec type only. r=jya (ca48d110f4)
- Bug 1243538: P6. Adjust gonk decoder to allow different decoding size from metadata. r=alfredo (257e017762)
- Bug 1243538: [webm] P7. Let the decoder handle picture resizing. r=SingingTree (32dc4a5aac)
- Bug 1262727: [webm] Ensure first frame returned after seek is a keyframe. r=kinetik (f16140852a)
- Bug 1246536: [webm] Only use discard padding information on last packet. r=kinetik (0bac4f8855)
- Bug 1266013: Fix Firefox OS compile errors. r=gerald (f021717287)
- cleanup (390cdec6ee)
- Bug 1264991: Don't construct invalid channel configuration. r=gerald (661828e8b8)
- Bug 1265093: Fix CID 1358648. r=gerald (55468c1261)
- Bug 1262659 - Report HTTP Live Streaming playback requests. r=cpearce,bsmedberg (96b8cd2810)
- Bug 1265400 - Use unsigned long for AudioBuffer length and numberOfChannels; r=smaug (f74f27ea4e)
2024-05-18 23:12:44 +08:00

2411 lines
64 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/Console.h"
#include "mozilla/dom/ConsoleBinding.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/Maybe.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDocument.h"
#include "nsDOMNavigationTiming.h"
#include "nsGlobalWindow.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "nsPerformance.h"
#include "ScriptSettings.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "xpcprivate.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsProxyRelease.h"
#include "mozilla/ConsoleTimelineMarker.h"
#include "mozilla/TimestampTimelineMarker.h"
#include "nsIConsoleAPIStorage.h"
#include "nsIDOMWindowUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadContext.h"
#include "nsIProgrammingLanguage.h"
#include "nsISensitiveInfoHiddenURI.h"
#include "nsIServiceManager.h"
#include "nsISupportsPrimitives.h"
#include "nsIWebNavigation.h"
#include "nsIXPConnect.h"
// The maximum allowed number of concurrent timers per page.
#define MAX_PAGE_TIMERS 10000
// The maximum allowed number of concurrent counters per page.
#define MAX_PAGE_COUNTERS 10000
// The maximum stacktrace depth when populating the stacktrace array used for
// console.trace().
#define DEFAULT_MAX_STACKTRACE_DEPTH 200
// This tags are used in the Structured Clone Algorithm to move js values from
// worker thread to main thread
#define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
// This value is taken from ConsoleAPIStorage.js
#define STORAGE_MAX_EVENTS 1000
using namespace mozilla::dom::exceptions;
using namespace mozilla::dom::workers;
namespace mozilla {
namespace dom {
struct
ConsoleStructuredCloneData
{
nsCOMPtr<nsISupports> mParent;
nsTArray<RefPtr<BlobImpl>> mBlobs;
};
/**
* Console API in workers uses the Structured Clone Algorithm to move any value
* from the worker thread to the main-thread. Some object cannot be moved and,
* in these cases, we convert them to strings.
* It's not the best, but at least we are able to show something.
*/
class ConsoleCallData final
{
public:
NS_INLINE_DECL_REFCOUNTING(ConsoleCallData)
ConsoleCallData()
: mMethodName(Console::MethodLog)
, mPrivate(false)
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
, mStartTimerValue(0)
, mStartTimerStatus(false)
, mStopTimerDuration(0)
, mStopTimerStatus(false)
, mCountValue(MAX_PAGE_COUNTERS)
, mIDType(eUnknown)
, mOuterIDNumber(0)
, mInnerIDNumber(0)
#ifdef DEBUG
, mOwningThread(PR_GetCurrentThread())
#endif
{}
bool
Initialize(JSContext* aCx, Console::MethodName aName,
const nsAString& aString,
const Sequence<JS::Value>& aArguments,
Console* aConsole)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aConsole);
// We must be registered before doing any JS operation otherwise it can
// happen that mCopiedArguments are not correctly traced.
aConsole->StoreCallData(this);
mMethodName = aName;
mMethodString = aString;
mGlobal = JS::CurrentGlobalOrNull(aCx);
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
aConsole->UnstoreCallData(this);
return false;
}
}
return true;
}
void
SetIDs(uint64_t aOuterID, uint64_t aInnerID)
{
MOZ_ASSERT(mIDType == eUnknown);
mOuterIDNumber = aOuterID;
mInnerIDNumber = aInnerID;
mIDType = eNumber;
}
void
SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
{
MOZ_ASSERT(mIDType == eUnknown);
mOuterIDString = aOuterID;
mInnerIDString = aInnerID;
mIDType = eString;
}
void
SetOriginAttributes(const OriginAttributes& aOriginAttributes)
{
mOriginAttributes = aOriginAttributes;
}
bool
PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const
{
AssertIsOnOwningThread();
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
fallible))) {
return false;
}
}
return true;
}
void
Trace(const TraceCallbacks& aCallbacks, void* aClosure)
{
AssertIsOnOwningThread();
ConsoleCallData* tmp = this;
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
}
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal);
}
void
AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread);
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
JS::Heap<JSObject*> mGlobal;
// This is a copy of the arguments we received from the DOM bindings. Console
// object traces them because this ConsoleCallData calls
// RegisterConsoleCallData() in the Initialize().
nsTArray<JS::Heap<JS::Value>> mCopiedArguments;
Console::MethodName mMethodName;
bool mPrivate;
int64_t mTimeStamp;
// These values are set in the owning thread and they contain the timestamp of
// when the new timer has started, the name of it and the status of the
// creation of it. If status is false, something went wrong. User
// DOMHighResTimeStamp instead mozilla::TimeStamp because we use
// monotonicTimer from Performance.now();
// They will be set on the owning thread and never touched again on that
// thread. They will be used in order to create a ConsoleTimerStart dictionary
// when console.time() is used.
DOMHighResTimeStamp mStartTimerValue;
nsString mStartTimerLabel;
bool mStartTimerStatus;
// These values are set in the owning thread and they contain the duration,
// the name and the status of the StopTimer method. If status is false,
// something went wrong. They will be set on the owning thread and never
// touched again on that thread. They will be used in order to create a
// ConsoleTimerEnd dictionary. This members are set when
// console.timeEnd() is called.
double mStopTimerDuration;
nsString mStopTimerLabel;
bool mStopTimerStatus;
// These 2 values are set by IncreaseCounter on the owning thread and they are
// used CreateCounterValue. These members are set when console.count() is
// called.
nsString mCountLabel;
uint32_t mCountValue;
// The concept of outerID and innerID is misleading because when a
// ConsoleCallData is created from a window, these are the window IDs, but
// when the object is created from a SharedWorker, a ServiceWorker or a
// subworker of a ChromeWorker these IDs are the type of worker and the
// filename of the callee.
// In Console.jsm the ID is 'jsm'.
enum {
eString,
eNumber,
eUnknown
} mIDType;
uint64_t mOuterIDNumber;
nsString mOuterIDString;
uint64_t mInnerIDNumber;
nsString mInnerIDString;
OriginAttributes mOriginAttributes;
nsString mMethodString;
// Stack management is complicated, because we want to do it as
// lazily as possible. Therefore, we have the following behavior:
// 1) mTopStackFrame is initialized whenever we have any JS on the stack
// 2) mReifiedStack is initialized if we're created in a worker.
// 3) mStack is set (possibly to null if there is no JS on the stack) if
// we're created on main thread.
Maybe<ConsoleStackEntry> mTopStackFrame;
Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
nsCOMPtr<nsIStackFrame> mStack;
// mStatus is about the lifetime of this object. Console must take care of
// keep it alive or not following this enumeration.
enum {
// If the object is created but it is owned by some runnable, this is its
// status. It can be deleted at any time.
eUnused,
// When a runnable takes ownership of a ConsoleCallData and send it to
// different thread, this is its status. Console cannot delete it at this
// time.
eInUse,
// When a runnable owns this ConsoleCallData, we can't delete it directly.
// instead, we mark it with this new status and we move it in
// mCallDataStoragePending list in order to keep it alive an trace it
// correctly. Once the runnable finishs its task, it will delete this object
// calling ReleaseCallData().
eToBeDeleted
} mStatus;
#ifdef DEBUG
PRThread* mOwningThread;
#endif
private:
~ConsoleCallData()
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus != eInUse);
}
};
// This class is used to clear any exception at the end of this method.
class ClearException
{
public:
explicit ClearException(JSContext* aCx)
: mCx(aCx)
{
}
~ClearException()
{
JS_ClearPendingException(mCx);
}
private:
JSContext* mCx;
};
class ConsoleRunnable : public nsRunnable
, public WorkerFeature
, public StructuredCloneHolderBase
{
public:
explicit ConsoleRunnable(Console* aConsole)
: mWorkerPrivate(GetCurrentThreadWorkerPrivate())
, mConsole(aConsole)
{
MOZ_ASSERT(mWorkerPrivate);
}
virtual
~ConsoleRunnable()
{
// Clear the StructuredCloneHolderBase class.
Clear();
}
bool
Dispatch(JSContext* aCx)
{
if (!DispatchInternal(aCx)) {
ReleaseData();
return false;
}
return true;
}
virtual bool Notify(workers::Status aStatus) override
{
// We don't care about the notification. We just want to keep the
// mWorkerPrivate alive.
return true;
}
private:
NS_IMETHOD
Run() override
{
AssertIsOnMainThread();
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindow* window = wp->GetWindow();
if (!window) {
RunWindowless();
} else {
RunWithWindow(window);
}
PostDispatch();
return NS_OK;
}
bool
DispatchInternal(JSContext* aCx)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (NS_WARN_IF(!PreDispatch(aCx))) {
return false;
}
if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
return false;
}
if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) {
return false;
}
return true;
}
void
PostDispatch()
{
class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
{
RefPtr<ConsoleRunnable> mRunnable;
public:
ConsoleReleaseRunnable(WorkerPrivate* aWorkerPrivate,
ConsoleRunnable* aRunnable)
: MainThreadWorkerControlRunnable(aWorkerPrivate)
, mRunnable(aRunnable)
{
MOZ_ASSERT(aRunnable);
}
// If something goes wrong, we still need to release the ConsoleCallData
// object. For this reason we have a custom Cancel method.
nsresult
Cancel() override
{
mRunnable->ReleaseData();
mRunnable->mConsole = nullptr;
return NS_OK;
}
virtual bool
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
Cancel();
aWorkerPrivate->RemoveFeature(mRunnable);
return true;
}
private:
~ConsoleReleaseRunnable()
{}
};
RefPtr<WorkerControlRunnable> runnable =
new ConsoleReleaseRunnable(mWorkerPrivate, this);
NS_WARN_IF(!runnable->Dispatch());
}
void
RunWithWindow(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
AutoJSAPI jsapi;
MOZ_ASSERT(aWindow);
RefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
if (NS_WARN_IF(!jsapi.Init(win))) {
return;
}
MOZ_ASSERT(aWindow->IsInnerWindow());
nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
if (NS_WARN_IF(!outerWindow)) {
return;
}
RunConsole(jsapi.cx(), outerWindow, aWindow);
}
void
RunWindowless()
{
AssertIsOnMainThread();
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
MOZ_ASSERT(!wp->GetWindow());
AutoSafeJSContext cx;
JS::Rooted<JSObject*> global(cx, mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal()));
if (NS_WARN_IF(!global)) {
return;
}
// The CreateSandbox call returns a proxy to the actual sandbox object. We
// don't need a proxy here.
global = js::UncheckedUnwrap(global);
JSAutoCompartment ac(cx, global);
RunConsole(cx, nullptr, nullptr);
}
protected:
// This method is called in the owning thread of the Console object.
virtual bool
PreDispatch(JSContext* aCx) = 0;
// This method is called in the main-thread.
virtual void
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) = 0;
// This method is called in the owning thread of the Console object.
virtual void
ReleaseData() = 0;
virtual JSObject* CustomReadHandler(JSContext* aCx,
JSStructuredCloneReader* aReader,
uint32_t aTag,
uint32_t aIndex) override
{
AssertIsOnMainThread();
if (aTag == CONSOLE_TAG_BLOB) {
MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
JS::Rooted<JS::Value> val(aCx);
{
RefPtr<Blob> blob =
Blob::Create(mClonedData.mParent, mClonedData.mBlobs.ElementAt(aIndex));
if (!ToJSValue(aCx, blob, &val)) {
return nullptr;
}
}
return &val.toObject();
}
MOZ_CRASH("No other tags are supported.");
return nullptr;
}
virtual bool CustomWriteHandler(JSContext* aCx,
JSStructuredCloneWriter* aWriter,
JS::Handle<JSObject*> aObj) override
{
RefPtr<Blob> blob;
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
blob->Impl()->MayBeClonedToOtherThreads()) {
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
mClonedData.mBlobs.Length()))) {
return false;
}
mClonedData.mBlobs.AppendElement(blob->Impl());
return true;
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (NS_WARN_IF(!jsString)) {
return false;
}
if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
return false;
}
return true;
}
WorkerPrivate* mWorkerPrivate;
// This must be released on the worker thread.
RefPtr<Console> mConsole;
ConsoleStructuredCloneData mClonedData;
};
// This runnable appends a CallData object into the Console queue running on
// the main-thread.
class ConsoleCallDataRunnable final : public ConsoleRunnable
{
public:
ConsoleCallDataRunnable(Console* aConsole,
ConsoleCallData* aCallData)
: ConsoleRunnable(aConsole)
, mCallData(aCallData)
{
MOZ_ASSERT(aCallData);
mWorkerPrivate->AssertIsOnWorkerThread();
mCallData->AssertIsOnOwningThread();
}
private:
~ConsoleCallDataRunnable()
{
MOZ_ASSERT(!mCallData);
}
bool
PreDispatch(JSContext* aCx) override
{
mWorkerPrivate->AssertIsOnWorkerThread();
mCallData->AssertIsOnOwningThread();
ClearException ce(aCx);
JS::Rooted<JSObject*> arguments(aCx,
JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
if (NS_WARN_IF(!arguments)) {
return false;
}
JS::Rooted<JS::Value> arg(aCx);
for (uint32_t i = 0; i < mCallData->mCopiedArguments.Length(); ++i) {
arg = mCallData->mCopiedArguments[i];
if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
JSPROP_ENUMERATE))) {
return false;
}
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
if (NS_WARN_IF(!Write(aCx, value))) {
return false;
}
mCallData->mStatus = ConsoleCallData::eInUse;
return true;
}
void
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) override
{
AssertIsOnMainThread();
// The windows have to run in parallel.
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
if (aOuterWindow) {
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
// Save the principal's OriginAttributes in the console event data
// so that we will be able to filter messages by origin attributes.
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aInnerWindow);
if (NS_WARN_IF(!sop)) {
return;
}
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
if (NS_WARN_IF(!principal)) {
return;
}
mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
} else {
ConsoleStackEntry frame;
if (mCallData->mTopStackFrame) {
frame = *mCallData->mTopStackFrame;
}
nsString id = frame.mFilename;
nsString innerID;
if (mWorkerPrivate->IsSharedWorker()) {
innerID = NS_LITERAL_STRING("SharedWorker");
} else if (mWorkerPrivate->IsServiceWorker()) {
innerID = NS_LITERAL_STRING("ServiceWorker");
// Use scope as ID so the webconsole can decide if the message should
// show up per tab
id.AssignWithConversion(mWorkerPrivate->WorkerName());
} else {
innerID = NS_LITERAL_STRING("Worker");
}
mCallData->SetIDs(id, innerID);
// Save the principal's OriginAttributes in the console event data
// so that we will be able to filter messages by origin attributes.
nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
if (NS_WARN_IF(!principal)) {
return;
}
mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
}
// Now we could have the correct window (if we are not window-less).
mClonedData.mParent = aInnerWindow;
ProcessCallData(aCx);
mClonedData.mParent = nullptr;
}
virtual void
ReleaseData() override
{
mConsole->AssertIsOnOwningThread();
if (mCallData->mStatus == ConsoleCallData::eToBeDeleted) {
mConsole->ReleaseCallData(mCallData);
} else {
MOZ_ASSERT(mCallData->mStatus == ConsoleCallData::eInUse);
mCallData->mStatus = ConsoleCallData::eUnused;
}
mCallData = nullptr;
}
void
ProcessCallData(JSContext* aCx)
{
AssertIsOnMainThread();
ClearException ce(aCx);
JS::Rooted<JS::Value> argumentsValue(aCx);
if (!Read(aCx, &argumentsValue)) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
uint32_t length;
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
return;
}
Sequence<JS::Value> values;
SequenceRooter<JS::Value> arguments(aCx, &values);
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(aCx);
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
return;
}
if (!values.AppendElement(value, fallible)) {
return;
}
}
MOZ_ASSERT(values.Length() == length);
mConsole->ProcessCallData(aCx, mCallData, values);
}
RefPtr<ConsoleCallData> mCallData;
};
// This runnable calls ProfileMethod() on the console on the main-thread.
class ConsoleProfileRunnable final : public ConsoleRunnable
{
public:
ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
const Sequence<JS::Value>& aArguments)
: ConsoleRunnable(aConsole)
, mAction(aAction)
, mArguments(aArguments)
{
MOZ_ASSERT(aConsole);
}
private:
bool
PreDispatch(JSContext* aCx) override
{
ClearException ce(aCx);
JS::Rooted<JSObject*> arguments(aCx,
JS_NewArrayObject(aCx, mArguments.Length()));
if (NS_WARN_IF(!arguments)) {
return false;
}
JS::Rooted<JS::Value> arg(aCx);
for (uint32_t i = 0; i < mArguments.Length(); ++i) {
arg = mArguments[i];
if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
JSPROP_ENUMERATE))) {
return false;
}
}
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
if (NS_WARN_IF(!Write(aCx, value))) {
return false;
}
return true;
}
void
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
nsPIDOMWindow* aInnerWindow) override
{
MOZ_ASSERT(NS_IsMainThread());
ClearException ce(aCx);
// Now we could have the correct window (if we are not window-less).
mClonedData.mParent = aInnerWindow;
JS::Rooted<JS::Value> argumentsValue(aCx);
bool ok = Read(aCx, &argumentsValue);
mClonedData.mParent = nullptr;
if (!ok) {
return;
}
MOZ_ASSERT(argumentsValue.isObject());
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
uint32_t length;
if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
return;
}
Sequence<JS::Value> arguments;
for (uint32_t i = 0; i < length; ++i) {
JS::Rooted<JS::Value> value(aCx);
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
return;
}
if (!arguments.AppendElement(value, fallible)) {
return;
}
}
mConsole->ProfileMethod(aCx, mAction, arguments);
}
virtual void
ReleaseData() override
{}
nsString mAction;
// This is a reference of the sequence of arguments we receive from the DOM
// bindings and it's rooted by them. It's only used on the owning thread in
// PreDispatch().
const Sequence<JS::Value>& mArguments;
};
NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
// We don't need to traverse/unlink mStorage and mSandbox because they are not
// CCed objects and they are only used on the main thread, even when this
// Console object is used on workers.
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
}
for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
}
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
/* static */ already_AddRefed<Console>
Console::Create(nsPIDOMWindow* aWindow, ErrorResult& aRv)
{
RefPtr<Console> console = new Console(aWindow);
console->Initialize(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return console.forget();
}
Console::Console(nsPIDOMWindow* aWindow)
: mWindow(aWindow)
#ifdef DEBUG
, mOwningThread(PR_GetCurrentThread())
#endif
, mOuterID(0)
, mInnerID(0)
, mStatus(eUnknown)
{
if (mWindow) {
MOZ_ASSERT(mWindow->IsInnerWindow());
mInnerID = mWindow->WindowID();
// Without outerwindow any console message coming from this object will not
// shown in the devtools webconsole. But this should be fine because
// probably we are shutting down, or the window is CCed/GCed.
nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
if (outerWindow) {
mOuterID = outerWindow->WindowID();
}
}
mozilla::HoldJSObjects(this);
}
Console::~Console()
{
AssertIsOnOwningThread();
Shutdown();
mozilla::DropJSObjects(this);
}
void
Console::Initialize(ErrorResult& aRv)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eUnknown);
if (NS_IsMainThread()) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (NS_WARN_IF(!obs)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = obs->AddObserver(this, "inner-window-destroyed", true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aRv = obs->AddObserver(this, "memory-pressure", true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
mStatus = eInitialized;
}
void
Console::Shutdown()
{
AssertIsOnOwningThread();
if (mStatus == eUnknown || mStatus == eShuttingDown) {
return;
}
if (NS_IsMainThread()) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "inner-window-destroyed");
obs->RemoveObserver(this, "memory-pressure");
}
}
NS_ReleaseOnMainThread(mStorage.forget());
NS_ReleaseOnMainThread(mSandbox.forget());
mTimerRegistry.Clear();
mCounterRegistry.Clear();
mCallDataStorage.Clear();
mCallDataStoragePending.Clear();
mStatus = eShuttingDown;
}
NS_IMETHODIMP
Console::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
AssertIsOnMainThread();
if (!strcmp(aTopic, "inner-window-destroyed")) {
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
uint64_t innerID;
nsresult rv = wrapper->GetData(&innerID);
NS_ENSURE_SUCCESS(rv, rv);
if (innerID == mInnerID) {
Shutdown();
}
return NS_OK;
}
if (!strcmp(aTopic, "memory-pressure")) {
ClearStorage();
return NS_OK;
}
return NS_OK;
}
void
Console::ClearStorage()
{
mCallDataStorage.Clear();
}
JSObject*
Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return ConsoleBinding::Wrap(aCx, this, aGivenProto);
}
#define METHOD(name, string) \
void \
Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
{ \
Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
}
METHOD(Log, "log")
METHOD(Info, "info")
METHOD(Warn, "warn")
METHOD(Error, "error")
METHOD(Exception, "exception")
METHOD(Debug, "debug")
METHOD(Table, "table")
METHOD(Clear, "clear")
void
Console::Trace(JSContext* aCx)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
const Sequence<JS::Value> data;
Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
}
// Displays an interactive listing of all the properties of an object.
METHOD(Dir, "dir");
METHOD(Dirxml, "dirxml");
METHOD(Group, "group")
METHOD(GroupCollapsed, "groupCollapsed")
METHOD(GroupEnd, "groupEnd")
void
Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data);
if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
return;
}
Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
}
void
Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data);
if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
return;
}
Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
}
void
Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
Sequence<JS::Value> data;
SequenceRooter<JS::Value> rooter(aCx, &data);
if (aData.isString() && !data.AppendElement(aData, fallible)) {
return;
}
Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
}
void
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
}
void
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
}
void
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
const Sequence<JS::Value>& aData)
{
MOZ_ASSERT(mStatus == eInitialized);
if (!NS_IsMainThread()) {
// Here we are in a worker thread.
RefPtr<ConsoleProfileRunnable> runnable =
new ConsoleProfileRunnable(this, aAction, aData);
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
runnable->Dispatch(aCx);
return;
}
ClearException ce(aCx);
RootedDictionary<ConsoleProfileEvent> event(aCx);
event.mAction = aAction;
event.mArguments.Construct();
Sequence<JS::Value>& sequence = event.mArguments.Value();
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (!sequence.AppendElement(aData[i], fallible)) {
return;
}
}
JS::Rooted<JS::Value> eventValue(aCx);
if (!ToJSValue(aCx, event, &eventValue)) {
return;
}
JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
MOZ_ASSERT(eventObj);
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
JSPROP_ENUMERATE)) {
return;
}
nsXPConnect* xpc = nsXPConnect::XPConnect();
nsCOMPtr<nsISupports> wrapper;
const nsIID& iid = NS_GET_IID(nsISupports);
if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
return;
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
}
}
void
Console::Assert(JSContext* aCx, bool aCondition,
const Sequence<JS::Value>& aData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
if (!aCondition) {
Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
}
}
METHOD(Count, "count")
void
Console::NoopMethod()
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
// Nothing to do.
}
static
nsresult
StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
ConsoleStackEntry& aStackEntry)
{
MOZ_ASSERT(aStackFrame);
nsresult rv = aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
NS_ENSURE_SUCCESS(rv, rv);
int32_t lineNumber;
rv = aStackFrame->GetLineNumber(aCx, &lineNumber);
NS_ENSURE_SUCCESS(rv, rv);
aStackEntry.mLineNumber = lineNumber;
int32_t columnNumber;
rv = aStackFrame->GetColumnNumber(aCx, &columnNumber);
NS_ENSURE_SUCCESS(rv, rv);
aStackEntry.mColumnNumber = columnNumber;
rv = aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
NS_ENSURE_SUCCESS(rv, rv);
nsString cause;
rv = aStackFrame->GetAsyncCause(aCx, cause);
NS_ENSURE_SUCCESS(rv, rv);
if (!cause.IsEmpty()) {
aStackEntry.mAsyncCause.Construct(cause);
}
aStackEntry.mLanguage = nsIProgrammingLanguage::JAVASCRIPT;
return NS_OK;
}
static
nsresult
ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
nsTArray<ConsoleStackEntry>& aRefiedStack)
{
nsCOMPtr<nsIStackFrame> stack(aStack);
while (stack) {
ConsoleStackEntry& data = *aRefiedStack.AppendElement();
nsresult rv = StackFrameToStackEntry(aCx, stack, data);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStackFrame> caller;
rv = stack->GetCaller(aCx, getter_AddRefs(caller));
NS_ENSURE_SUCCESS(rv, rv);
if (!caller) {
rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
NS_ENSURE_SUCCESS(rv, rv);
}
stack.swap(caller);
}
return NS_OK;
}
// Queue a call to a console method. See the CALL_DELAY constant.
void
Console::Method(JSContext* aCx, MethodName aMethodName,
const nsAString& aMethodString,
const Sequence<JS::Value>& aData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(mStatus == eInitialized);
RefPtr<ConsoleCallData> callData(new ConsoleCallData());
ClearException ce(aCx);
if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
aData, this))) {
return;
}
if (mWindow) {
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
if (!webNav) {
return;
}
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
MOZ_ASSERT(loadContext);
loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
// Save the principal's OriginAttributes in the console event data
// so that we will be able to filter messages by origin attributes.
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow);
if (NS_WARN_IF(!sop)) {
return;
}
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
if (NS_WARN_IF(!principal)) {
return;
}
callData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
}
uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ?
DEFAULT_MAX_STACKTRACE_DEPTH : 1;
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
if (stack) {
callData->mTopStackFrame.emplace();
nsresult rv = StackFrameToStackEntry(aCx, stack,
*callData->mTopStackFrame);
if (NS_FAILED(rv)) {
return;
}
}
if (NS_IsMainThread()) {
callData->mStack = stack;
} else {
// nsIStackFrame is not threadsafe, so we need to snapshot it now,
// before we post our runnable to the main thread.
callData->mReifiedStack.emplace();
nsresult rv = ReifyStack(aCx, stack, *callData->mReifiedStack);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
DOMHighResTimeStamp monotonicTimer;
// Monotonic timer for 'time' and 'timeEnd'
if (aMethodName == MethodTime ||
aMethodName == MethodTimeEnd ||
aMethodName == MethodTimeStamp) {
if (mWindow) {
nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
MOZ_ASSERT(win);
RefPtr<nsPerformance> performance = win->GetPerformance();
if (!performance) {
return;
}
monotonicTimer = performance->Now();
nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
// The 'timeStamp' recordings do not need an argument; use empty string
// if no arguments passed in.
if (isTimelineRecording && aMethodName == MethodTimeStamp) {
JS::Rooted<JS::Value> value(aCx, aData.Length() == 0
? JS_GetEmptyStringValue(aCx)
: aData[0]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
nsAutoJSString key;
if (jsString) {
key.init(aCx, jsString);
}
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<TimestampTimelineMarker>(key)));
}
// For `console.time(foo)` and `console.timeEnd(foo)`.
else if (isTimelineRecording && aData.Length() == 1) {
JS::Rooted<JS::Value> value(aCx, aData[0]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (jsString) {
nsAutoJSString key;
if (key.init(aCx, jsString)) {
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<ConsoleTimelineMarker>(
key, aMethodName == MethodTime ? MarkerTracingType::START
: MarkerTracingType::END)));
}
}
}
} else {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
TimeDuration duration =
mozilla::TimeStamp::Now() - workerPrivate->NowBaseTimeStamp();
monotonicTimer = duration.ToMilliseconds();
}
}
if (aMethodName == MethodTime && !aData.IsEmpty()) {
callData->mStartTimerStatus = StartTimer(aCx, aData[0],
monotonicTimer,
callData->mStartTimerLabel,
&callData->mStartTimerValue);
}
else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
callData->mStopTimerStatus = StopTimer(aCx, aData[0],
monotonicTimer,
callData->mStopTimerLabel,
&callData->mStopTimerDuration);
}
else if (aMethodName == MethodCount) {
ConsoleStackEntry frame;
if (callData->mTopStackFrame) {
frame = *callData->mTopStackFrame;
}
callData->mCountValue = IncreaseCounter(aCx, frame, aData,
callData->mCountLabel);
}
if (NS_IsMainThread()) {
callData->SetIDs(mOuterID, mInnerID);
ProcessCallData(aCx, callData, aData);
// Just because we don't want to expose
// retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
// cleanup the mCallDataStorage:
UnstoreCallData(callData);
return;
}
// We do this only in workers for now.
NotifyHandler(aCx, aData, callData);
RefPtr<ConsoleCallDataRunnable> runnable =
new ConsoleCallDataRunnable(this, callData);
NS_WARN_IF(!runnable->Dispatch(aCx));
}
// We store information to lazily compute the stack in the reserved slots of
// LazyStackGetter. The first slot always stores a JS object: it's either the
// JS wrapper of the nsIStackFrame or the actual reified stack representation.
// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
// reified the stack yet, or an UndefinedValue() otherwise.
enum {
SLOT_STACKOBJ,
SLOT_RAW_STACK
};
bool
LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
JS::Rooted<JSObject*> callee(aCx, &args.callee());
JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
if (v.isUndefined()) {
// Already reified.
args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
return true;
}
nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
nsTArray<ConsoleStackEntry> reifiedStack;
nsresult rv = ReifyStack(aCx, stack, reifiedStack);
if (NS_WARN_IF(NS_FAILED(rv))) {
Throw(aCx, rv);
return false;
}
JS::Rooted<JS::Value> stackVal(aCx);
if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
return false;
}
MOZ_ASSERT(stackVal.isObject());
js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
args.rval().set(stackVal);
return true;
}
void
Console::ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
const Sequence<JS::Value>& aArguments)
{
AssertIsOnMainThread();
MOZ_ASSERT(aData);
JS::Rooted<JS::Value> eventValue(aCx);
// We want to create a console event object and pass it to our
// nsIConsoleAPIStorage implementation. We want to define some accessor
// properties on this object, and those will need to keep an nsIStackFrame
// alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
// further, passing untrusted objects to system code is likely to run afoul of
// Object Xrays. So we want to wrap in a system-principal scope here. But
// which one? We could cheat and try to get the underlying JSObject* of
// mStorage, but that's a bit fragile. Instead, we just use the junk scope,
// with explicit permission from the XPConnect module owner. If you're
// tempted to do that anywhere else, talk to said module owner first.
// aCx and aArguments are in the same compartment.
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
xpc::PrivilegedJunkScope(),
&eventValue, aData))) {
return;
}
if (!mStorage) {
mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
}
if (!mStorage) {
NS_WARNING("Failed to get the ConsoleAPIStorage service.");
return;
}
nsAutoString innerID, outerID;
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
if (aData->mIDType == ConsoleCallData::eString) {
outerID = aData->mOuterIDString;
innerID = aData->mInnerIDString;
} else {
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
outerID.AppendInt(aData->mOuterIDNumber);
innerID.AppendInt(aData->mInnerIDNumber);
}
if (aData->mMethodName == MethodClear) {
nsresult rv = mStorage->ClearEvents(innerID);
NS_WARN_IF(NS_FAILED(rv));
}
if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
NS_WARNING("Failed to record a console event.");
}
}
bool
Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
const Sequence<JS::Value>& aArguments,
JSObject* aTargetScope,
JS::MutableHandle<JS::Value> aEventValue,
ConsoleCallData* aData) const
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(aData);
MOZ_ASSERT(aTargetScope);
JS::Rooted<JSObject*> targetScope(aCx, aTargetScope);
ConsoleStackEntry frame;
if (aData->mTopStackFrame) {
frame = *aData->mTopStackFrame;
}
ClearException ce(aCx);
RootedDictionary<ConsoleEvent> event(aCx);
// Save the principal's OriginAttributes in the console event data
// so that we will be able to filter messages by origin attributes.
JS::Rooted<JS::Value> originAttributesValue(aCx);
if (ToJSValue(aCx, aData->mOriginAttributes, &originAttributesValue)) {
event.mOriginAttributes = originAttributesValue;
}
event.mID.Construct();
event.mInnerID.Construct();
if (aData->mIDType == ConsoleCallData::eString) {
event.mID.Value().SetAsString() = aData->mOuterIDString;
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
} else if (aData->mIDType == ConsoleCallData::eNumber) {
event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
} else {
// aData->mIDType can be eUnknown when we dispatch notifications via
// mConsoleEventNotifier.
event.mID.Value().SetAsUnsignedLongLong() = 0;
event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
}
event.mLevel = aData->mMethodString;
event.mFilename = frame.mFilename;
nsCOMPtr<nsIURI> filenameURI;
nsAutoCString pass;
if (NS_IsMainThread() &&
NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
nsAutoCString spec;
if (safeURI &&
NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
CopyUTF8toUTF16(spec, event.mFilename);
}
}
event.mLineNumber = frame.mLineNumber;
event.mColumnNumber = frame.mColumnNumber;
event.mFunctionName = frame.mFunctionName;
event.mTimeStamp = aData->mTimeStamp;
event.mPrivate = aData->mPrivate;
switch (aData->mMethodName) {
case MethodLog:
case MethodInfo:
case MethodWarn:
case MethodError:
case MethodException:
case MethodDebug:
case MethodAssert:
event.mArguments.Construct();
event.mStyles.Construct();
if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
event.mArguments.Value(),
event.mStyles.Value()))) {
return false;
}
break;
default:
event.mArguments.Construct();
if (NS_WARN_IF(!ArgumentsToValueList(aArguments,
event.mArguments.Value()))) {
return false;
}
}
if (aData->mMethodName == MethodGroup ||
aData->mMethodName == MethodGroupCollapsed ||
aData->mMethodName == MethodGroupEnd) {
ComposeGroupName(aCx, aArguments, event.mGroupName);
}
else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
aData->mStartTimerValue,
aData->mStartTimerStatus);
}
else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
aData->mStopTimerDuration,
aData->mStopTimerStatus);
}
else if (aData->mMethodName == MethodCount) {
event.mCounter = CreateCounterValue(aCx, aData->mCountLabel,
aData->mCountValue);
}
JSAutoCompartment ac2(aCx, targetScope);
if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
return false;
}
JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
JSPROP_ENUMERATE))) {
return false;
}
if (ShouldIncludeStackTrace(aData->mMethodName)) {
// Now define the "stacktrace" property on eventObj. There are two cases
// here. Either we came from a worker and have a reified stack, or we want
// to define a getter that will lazily reify the stack.
if (aData->mReifiedStack) {
JS::Rooted<JS::Value> stacktrace(aCx);
if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
JSPROP_ENUMERATE))) {
return false;
}
} else {
JSFunction* fun = js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0,
"stacktrace");
if (NS_WARN_IF(!fun)) {
return false;
}
JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
// We want to store our stack in the function and have it stay alive. But
// we also need sane access to the C++ nsIStackFrame. So store both a JS
// wrapper and the raw pointer: the former will keep the latter alive.
JS::Rooted<JS::Value> stackVal(aCx);
nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack,
&stackVal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
JS::PrivateValue(aData->mStack.get()));
if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace",
JS::UndefinedHandleValue,
JSPROP_ENUMERATE | JSPROP_SHARED |
JSPROP_GETTER | JSPROP_SETTER,
JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
nullptr))) {
return false;
}
}
}
return true;
}
namespace {
// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
bool
FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
{
if (!aOutput.IsEmpty()) {
JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
aOutput.get(),
aOutput.Length()));
if (NS_WARN_IF(!str)) {
return false;
}
if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
return false;
}
aOutput.Truncate();
}
return true;
}
} // namespace
bool
Console::ProcessArguments(JSContext* aCx,
const Sequence<JS::Value>& aData,
Sequence<JS::Value>& aSequence,
Sequence<nsString>& aStyles) const
{
if (aData.IsEmpty()) {
return true;
}
if (aData.Length() == 1 || !aData[0].isString()) {
return ArgumentsToValueList(aData, aSequence);
}
JS::Rooted<JS::Value> format(aCx, aData[0]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
if (NS_WARN_IF(!jsString)) {
return false;
}
nsAutoJSString string;
if (NS_WARN_IF(!string.init(aCx, jsString))) {
return false;
}
nsString::const_iterator start, end;
string.BeginReading(start);
string.EndReading(end);
nsString output;
uint32_t index = 1;
while (start != end) {
if (*start != '%') {
output.Append(*start);
++start;
continue;
}
++start;
if (start == end) {
output.Append('%');
break;
}
if (*start == '%') {
output.Append(*start);
++start;
continue;
}
nsAutoString tmp;
tmp.Append('%');
int32_t integer = -1;
int32_t mantissa = -1;
// Let's parse %<number>.<number> for %d and %f
if (*start >= '0' && *start <= '9') {
integer = 0;
do {
integer = integer * 10 + *start - '0';
tmp.Append(*start);
++start;
} while (*start >= '0' && *start <= '9' && start != end);
}
if (start == end) {
output.Append(tmp);
break;
}
if (*start == '.') {
tmp.Append(*start);
++start;
if (start == end) {
output.Append(tmp);
break;
}
// '.' must be followed by a number.
if (*start < '0' || *start > '9') {
output.Append(tmp);
continue;
}
mantissa = 0;
do {
mantissa = mantissa * 10 + *start - '0';
tmp.Append(*start);
++start;
} while (*start >= '0' && *start <= '9' && start != end);
if (start == end) {
output.Append(tmp);
break;
}
}
char ch = *start;
tmp.Append(ch);
++start;
switch (ch) {
case 'o':
case 'O':
{
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
return false;
}
JS::Rooted<JS::Value> v(aCx);
if (index < aData.Length()) {
v = aData[index++];
}
if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
return false;
}
break;
}
case 'c':
{
// If there isn't any output but there's already a style, then
// discard the previous style and use the next one instead.
if (output.IsEmpty() && !aStyles.IsEmpty()) {
aStyles.TruncateLength(aStyles.Length() - 1);
}
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
return false;
}
if (index < aData.Length()) {
JS::Rooted<JS::Value> v(aCx, aData[index++]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
if (NS_WARN_IF(!jsString)) {
return false;
}
int32_t diff = aSequence.Length() - aStyles.Length();
if (diff > 0) {
for (int32_t i = 0; i < diff; i++) {
if (NS_WARN_IF(!aStyles.AppendElement(NullString(), fallible))) {
return false;
}
}
}
nsAutoJSString string;
if (NS_WARN_IF(!string.init(aCx, jsString))) {
return false;
}
if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
return false;
}
}
break;
}
case 's':
if (index < aData.Length()) {
JS::Rooted<JS::Value> value(aCx, aData[index++]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (NS_WARN_IF(!jsString)) {
return false;
}
nsAutoJSString v;
if (NS_WARN_IF(!v.init(aCx, jsString))) {
return false;
}
output.Append(v);
}
break;
case 'd':
case 'i':
if (index < aData.Length()) {
JS::Rooted<JS::Value> value(aCx, aData[index++]);
int32_t v;
if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
return false;
}
nsCString format;
MakeFormatString(format, integer, mantissa, 'd');
output.AppendPrintf(format.get(), v);
}
break;
case 'f':
if (index < aData.Length()) {
JS::Rooted<JS::Value> value(aCx, aData[index++]);
double v;
if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
return false;
}
// nspr returns "nan", but we want to expose it as "NaN"
if (std::isnan(v)) {
output.AppendFloat(v);
} else {
nsCString format;
MakeFormatString(format, integer, mantissa, 'f');
output.AppendPrintf(format.get(), v);
}
}
break;
default:
output.Append(tmp);
break;
}
}
if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
return false;
}
// Discard trailing style element if there is no output to apply it to.
if (aStyles.Length() > aSequence.Length()) {
aStyles.TruncateLength(aSequence.Length());
}
// The rest of the array, if unused by the format string.
for (; index < aData.Length(); ++index) {
if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
return false;
}
}
return true;
}
void
Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
int32_t aMantissa, char aCh) const
{
aFormat.Append('%');
if (aInteger >= 0) {
aFormat.AppendInt(aInteger);
}
if (aMantissa >= 0) {
aFormat.Append('.');
aFormat.AppendInt(aMantissa);
}
aFormat.Append(aCh);
}
void
Console::ComposeGroupName(JSContext* aCx,
const Sequence<JS::Value>& aData,
nsAString& aName) const
{
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (i != 0) {
aName.AppendASCII(" ");
}
JS::Rooted<JS::Value> value(aCx, aData[i]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
if (!jsString) {
return;
}
nsAutoJSString string;
if (!string.init(aCx, jsString)) {
return;
}
aName.Append(string);
}
}
bool
Console::StartTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
DOMHighResTimeStamp* aTimerValue)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTimerValue);
*aTimerValue = 0;
if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
return false;
}
JS::Rooted<JS::Value> name(aCx, aName);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
if (NS_WARN_IF(!jsString)) {
return false;
}
nsAutoJSString label;
if (NS_WARN_IF(!label.init(aCx, jsString))) {
return false;
}
DOMHighResTimeStamp entry;
if (!mTimerRegistry.Get(label, &entry)) {
mTimerRegistry.Put(label, aTimestamp);
} else {
aTimestamp = entry;
}
aTimerLabel = label;
*aTimerValue = aTimestamp;
return true;
}
JS::Value
Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
DOMHighResTimeStamp aTimerValue,
bool aTimerStatus) const
{
if (!aTimerStatus) {
RootedDictionary<ConsoleTimerError> error(aCx);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, error, &value)) {
return JS::UndefinedValue();
}
return value;
}
RootedDictionary<ConsoleTimerStart> timer(aCx);
timer.mName = aTimerLabel;
timer.mStarted = aTimerValue;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, timer, &value)) {
return JS::UndefinedValue();
}
return value;
}
bool
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTimerDuration);
*aTimerDuration = 0;
JS::Rooted<JS::Value> name(aCx, aName);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
if (NS_WARN_IF(!jsString)) {
return false;
}
nsAutoJSString key;
if (NS_WARN_IF(!key.init(aCx, jsString))) {
return false;
}
DOMHighResTimeStamp entry;
if (NS_WARN_IF(!mTimerRegistry.Get(key, &entry))) {
return false;
}
mTimerRegistry.Remove(key);
aTimerLabel = key;
*aTimerDuration = aTimestamp - entry;
return true;
}
JS::Value
Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
double aDuration, bool aStatus) const
{
if (!aStatus) {
return JS::UndefinedValue();
}
RootedDictionary<ConsoleTimerEnd> timer(aCx);
timer.mName = aLabel;
timer.mDuration = aDuration;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, timer, &value)) {
return JS::UndefinedValue();
}
return value;
}
bool
Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
Sequence<JS::Value>& aSequence) const
{
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
return false;
}
}
return true;
}
uint32_t
Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
const Sequence<JS::Value>& aArguments,
nsAString& aCountLabel)
{
AssertIsOnOwningThread();
ClearException ce(aCx);
nsAutoString key;
nsAutoString label;
if (!aArguments.IsEmpty()) {
JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
nsAutoJSString string;
if (jsString && string.init(aCx, jsString)) {
label = string;
key = string;
}
}
if (key.IsEmpty()) {
key.Append(aFrame.mFilename);
key.Append(':');
key.AppendInt(aFrame.mLineNumber);
}
uint32_t count = 0;
if (!mCounterRegistry.Get(key, &count) &&
mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
return MAX_PAGE_COUNTERS;
}
++count;
mCounterRegistry.Put(key, count);
aCountLabel = label;
return count;
}
JS::Value
Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
uint32_t aCountValue) const
{
ClearException ce(aCx);
if (aCountValue == MAX_PAGE_COUNTERS) {
RootedDictionary<ConsoleCounterError> error(aCx);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, error, &value)) {
return JS::UndefinedValue();
}
return value;
}
RootedDictionary<ConsoleCounter> data(aCx);
data.mLabel = aCountLabel;
data.mCount = aCountValue;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, data, &value)) {
return JS::UndefinedValue();
}
return value;
}
bool
Console::ShouldIncludeStackTrace(MethodName aMethodName) const
{
switch (aMethodName) {
case MethodError:
case MethodException:
case MethodAssert:
case MethodTrace:
return true;
default:
return false;
}
}
JSObject*
Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
{
AssertIsOnMainThread();
if (!mSandbox) {
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
JS::Rooted<JSObject*> sandbox(aCx);
nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
mSandbox = new JSObjectHolder(aCx, sandbox);
}
return mSandbox->GetJSObject();
}
void
Console::StoreCallData(ConsoleCallData* aCallData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aCallData);
MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
mCallDataStorage.AppendElement(aCallData);
if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
RefPtr<ConsoleCallData> callData = mCallDataStorage[0];
mCallDataStorage.RemoveElementAt(0);
MOZ_ASSERT(callData->mStatus != ConsoleCallData::eToBeDeleted);
// We cannot delete this object now because we have to trace its JSValues
// until the pending operation (ConsoleCallDataRunnable) is completed.
if (callData->mStatus == ConsoleCallData::eInUse) {
callData->mStatus = ConsoleCallData::eToBeDeleted;
mCallDataStoragePending.AppendElement(callData);
}
}
}
void
Console::UnstoreCallData(ConsoleCallData* aCallData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aCallData);
MOZ_ASSERT(mCallDataStorage.Contains(aCallData));
MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
mCallDataStorage.RemoveElement(aCallData);
}
void
Console::ReleaseCallData(ConsoleCallData* aCallData)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aCallData);
MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eToBeDeleted);
MOZ_ASSERT(mCallDataStoragePending.Contains(aCallData));
mCallDataStoragePending.RemoveElement(aCallData);
}
void
Console::NotifyHandler(JSContext* aCx, const Sequence<JS::Value>& aArguments,
ConsoleCallData* aCallData) const
{
AssertIsOnOwningThread();
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aCallData);
if (!mConsoleEventNotifier) {
return;
}
JS::Rooted<JS::Value> value(aCx);
// aCx and aArguments are in the same compartment because this method is
// called directly when a Console.something() runs.
// mConsoleEventNotifier->Callable() is the scope where value will be sent to.
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
mConsoleEventNotifier->Callable(),
&value,
aCallData))) {
return;
}
JS::Rooted<JS::Value> ignored(aCx);
mConsoleEventNotifier->Call(value, &ignored);
}
void
Console::RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
ErrorResult& aRv)
{
AssertIsOnOwningThread();
// We don't want to expose this functionality to main-thread yet.
MOZ_ASSERT(!NS_IsMainThread());
JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
for (uint32_t i = 0; i < mCallDataStorage.Length(); ++i) {
JS::Rooted<JS::Value> value(aCx);
JS::Rooted<JSObject*> sequenceScope(aCx, mCallDataStorage[i]->mGlobal);
JSAutoCompartment ac(aCx, sequenceScope);
Sequence<JS::Value> sequence;
SequenceRooter<JS::Value> arguments(aCx, &sequence);
if (!mCallDataStorage[i]->PopulateArgumentsSequence(sequence)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
// Here we have aCx and sequence in the same compartment.
// targetScope is the destination scope and value will be populated in its
// compartment.
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, sequence,
targetScope,
&value,
mCallDataStorage[i]))) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aEvents.AppendElement(value);
}
}
void
Console::SetConsoleEventHandler(AnyCallback* aHandler)
{
AssertIsOnOwningThread();
// We don't want to expose this functionality to main-thread yet.
MOZ_ASSERT(!NS_IsMainThread());
mConsoleEventNotifier = aHandler;
}
void
Console::AssertIsOnOwningThread() const
{
MOZ_ASSERT(mOwningThread);
MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
}
} // namespace dom
} // namespace mozilla