mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
34074a23ec
- missing of Bug 1130096 - Convert embedding/components/windowwatcher/ to Gecko style (883cc280e) - Bug 1114299 - Pass window features up from the content process via nsCString. r=smaug (017de6dc8) - Bug 1114299 - Be more strict about which window chromeFlags we compute from content. r=smaug (71e9f4959) - Bug 1159034 - Only fake no-alpha for the backbuffer. - r=kamidphish (2bc4bcf14) - Bug 1184786 - Fix LastColorAttachment(). r=jgilber (b320f23de) - Bug 1184786 - Fix ReadBuffer parameter validation. r=jgilber (7c6fac544) - Bug 1151736 - More information to pick up with crash reports. r=jgilbert (cd26cb6cb) - Bug 1151736 - Lose context on OOM instead of crashing. r=jgilbert (904e2d98a) - Bug 1176153. Have binding code grab the array buffer view type for ArrayBufferView arguments, so consumers don't have to manually use JSAPI to do it. Use the new setup in WebGL code. r=smau (3a93669ee) - Bug 1182347 - Implement OriginAttributesPattern. r=sicking,f=allstars.chh (10eb6487c) - Bug 922212 - Add console.dirxml. r=bgrins, r=mrbkap (85ebd7e39) - Bug 1176341 - De-holder nsIXPConnect::CreateSandbox. r=baku,gabor (d27b571d5) - Bug 1156005 - Mark the nsIAtom members of NodeInfo::NodeInfoInner as MOZ_NON_OWNING_REF; r=baku (3c17318c4) - Bug 1156006 - Mark NodeInfo::mDocument as MOZ_NON_OWNING_REF; r=baku (67ad08801) - Bug 1169129 - Make DOMException::Create(nsresult, nsCString) accept nsACString. r=bholley (22e2f4e5e) - Bug 1156099 - Mark the atom members of NodeInfo as nsCOMPtr's; r=baku (83dca3dde) - Bug 1176757 - Throw a DataCloneError when attempting to invoke 'cloneNode' on a 'ShadowRoot' and associated mochitest. r=wchen (decac1493) - Bug 1169044 - Patch 6 - Update web-platform-tests expected data. r=jgraham (def441a11) - Bug 1169044 - Patch 7 - Set mObserver in URLSearchParams. a=bustage. (80bd7a589) - Bug 1181259 Remove stray debugging printf from Cache API. r=froydnj (8b030de0a) - Bug 1173439 P1 Store URLs as UTF8 strings in Cache instead of UTF16. r=ehsan (ad3120be5) - Bug 1180765 - Bump the caches.sqlite version numbers because of the field that was added in bug 1169044; r=jdm (c40b57eab) - Bug 1173439 P2 Parse Response URL query as a separate field. r=ehsan (3b5c9641b) - Bug 1173439 P3 Use url query field for database matching and Request creation. r=ehsan (18969bd6c) - Bug 1173439 P4 Remove Request url field from Cache API database. r=ehsan (00e45614d) - Bug 1173439 P5 Cache should index on a hash instead of the url itself. r=ehsan (eafb8d1a1) - Bug 1177916 - URLSearchParams::GetParentObject should not return nullptr, r=smaug (c44513ab6) - Bug 1177916 - URL should be a nsWrapperCache object, r=smaug (67c3f7221) - Bug 1121773 - Conditionally include SiteSpecificUserAgent files on mobile/android. (37db816f1)
1889 lines
47 KiB
C++
1889 lines
47 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/StructuredCloneHelper.h"
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsDocument.h"
|
|
#include "nsDOMNavigationTiming.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "nsJSUtils.h"
|
|
#include "nsPerformance.h"
|
|
#include "ScriptSettings.h"
|
|
#include "WorkerPrivate.h"
|
|
#include "WorkerRunnable.h"
|
|
#include "xpcprivate.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDocShell.h"
|
|
#include "nsProxyRelease.h"
|
|
|
|
#include "nsIConsoleAPIStorage.h"
|
|
#include "nsIDOMWindowUtils.h"
|
|
#include "nsIInterfaceRequestorUtils.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsIProgrammingLanguage.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsIWebNavigation.h"
|
|
#include "nsIXPConnect.h"
|
|
|
|
#ifdef MOZ_ENABLE_PROFILER_SPS
|
|
#include "nsIProfiler.h"
|
|
#endif
|
|
|
|
// 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
|
|
|
|
using namespace mozilla::dom::exceptions;
|
|
using namespace mozilla::dom::workers;
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
struct
|
|
ConsoleStructuredCloneData
|
|
{
|
|
nsCOMPtr<nsISupports> mParent;
|
|
nsTArray<nsRefPtr<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_THREADSAFE_REFCOUNTING(ConsoleCallData)
|
|
|
|
ConsoleCallData()
|
|
: mMethodName(Console::MethodLog)
|
|
, mPrivate(false)
|
|
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
|
|
, mIDType(eUnknown)
|
|
, mOuterIDNumber(0)
|
|
, mInnerIDNumber(0)
|
|
{ }
|
|
|
|
void
|
|
Initialize(JSContext* aCx, Console::MethodName aName,
|
|
const nsAString& aString, const Sequence<JS::Value>& aArguments)
|
|
{
|
|
mGlobal = JS::CurrentGlobalOrNull(aCx);
|
|
mMethodName = aName;
|
|
mMethodString = aString;
|
|
|
|
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
|
|
if (!mArguments.AppendElement(aArguments[i])) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
CleanupJSObjects()
|
|
{
|
|
mArguments.Clear();
|
|
mGlobal = nullptr;
|
|
}
|
|
|
|
JS::Heap<JSObject*> mGlobal;
|
|
|
|
Console::MethodName mMethodName;
|
|
bool mPrivate;
|
|
int64_t mTimeStamp;
|
|
DOMHighResTimeStamp mMonotonicTimer;
|
|
|
|
// 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;
|
|
|
|
nsString mMethodString;
|
|
nsTArray<JS::Heap<JS::Value>> mArguments;
|
|
|
|
// Stack management is complicated, because we want to do it as
|
|
// lazily as possible. Therefore, we have the following behavior:
|
|
// 1) mTopStackFrame is initialized whenever we have any JS on the stack
|
|
// 2) mReifiedStack is initialized if we're created in a worker.
|
|
// 3) mStack is set (possibly to null if there is no JS on the stack) if
|
|
// we're created on main thread.
|
|
Maybe<ConsoleStackEntry> mTopStackFrame;
|
|
Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
|
|
nsCOMPtr<nsIStackFrame> mStack;
|
|
|
|
private:
|
|
~ConsoleCallData()
|
|
{ }
|
|
};
|
|
|
|
// 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 StructuredCloneHelperInternal
|
|
{
|
|
public:
|
|
explicit ConsoleRunnable(Console* aConsole)
|
|
: mWorkerPrivate(GetCurrentThreadWorkerPrivate())
|
|
, mConsole(aConsole)
|
|
{
|
|
MOZ_ASSERT(mWorkerPrivate);
|
|
}
|
|
|
|
virtual
|
|
~ConsoleRunnable()
|
|
{
|
|
// Shutdown the StructuredCloneHelperInternal class.
|
|
Shutdown();
|
|
}
|
|
|
|
bool
|
|
Dispatch()
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
JSContext* cx = mWorkerPrivate->GetJSContext();
|
|
|
|
if (!PreDispatch(cx)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!mWorkerPrivate->AddFeature(cx, this))) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
|
|
return true;
|
|
}
|
|
|
|
virtual bool Notify(JSContext* aCx, 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()
|
|
{
|
|
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;
|
|
}
|
|
|
|
void
|
|
PostDispatch()
|
|
{
|
|
class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
|
|
{
|
|
nsRefPtr<ConsoleRunnable> mRunnable;
|
|
|
|
public:
|
|
ConsoleReleaseRunnable(WorkerPrivate* aWorkerPrivate,
|
|
ConsoleRunnable* aRunnable)
|
|
: MainThreadWorkerControlRunnable(aWorkerPrivate)
|
|
, mRunnable(aRunnable)
|
|
{
|
|
MOZ_ASSERT(aRunnable);
|
|
}
|
|
|
|
virtual bool
|
|
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
aWorkerPrivate->RemoveFeature(aCx, mRunnable);
|
|
mRunnable->mConsole = nullptr;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
~ConsoleReleaseRunnable()
|
|
{}
|
|
};
|
|
|
|
nsRefPtr<WorkerControlRunnable> runnable =
|
|
new ConsoleReleaseRunnable(mWorkerPrivate, this);
|
|
runnable->Dispatch(nullptr);
|
|
}
|
|
|
|
void
|
|
RunWithWindow(nsPIDOMWindow* aWindow)
|
|
{
|
|
AutoJSAPI jsapi;
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
|
|
if (NS_WARN_IF(!jsapi.Init(win))) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aWindow->IsInnerWindow());
|
|
nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
|
|
MOZ_ASSERT(outerWindow);
|
|
|
|
RunConsole(jsapi.cx(), outerWindow, aWindow);
|
|
}
|
|
|
|
void
|
|
RunWindowless()
|
|
{
|
|
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:
|
|
virtual bool
|
|
PreDispatch(JSContext* aCx) = 0;
|
|
|
|
virtual void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) = 0;
|
|
|
|
virtual JSObject* ReadCallback(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);
|
|
{
|
|
nsRefPtr<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 WriteCallback(JSContext* aCx,
|
|
JSStructuredCloneWriter* aWriter,
|
|
JS::Handle<JSObject*> aObj) override
|
|
{
|
|
nsRefPtr<Blob> blob;
|
|
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
|
|
blob->Impl()->MayBeClonedToOtherThreads()) {
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
if (!JS_WriteString(aWriter, jsString)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
WorkerPrivate* mWorkerPrivate;
|
|
|
|
// This must be released on the worker thread.
|
|
nsRefPtr<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)
|
|
{ }
|
|
|
|
private:
|
|
~ConsoleCallDataRunnable()
|
|
{
|
|
class ReleaseCallData final : public nsRunnable
|
|
{
|
|
public:
|
|
explicit ReleaseCallData(nsRefPtr<ConsoleCallData>& aCallData)
|
|
{
|
|
mCallData.swap(aCallData);
|
|
}
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
mCallData = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsRefPtr<ConsoleCallData> mCallData;
|
|
};
|
|
|
|
nsRefPtr<ReleaseCallData> runnable = new ReleaseCallData(mCallData);
|
|
if(NS_FAILED(NS_DispatchToMainThread(runnable))) {
|
|
NS_WARNING("Failed to dispatch a ReleaseCallData runnable. Leaking.");
|
|
}
|
|
}
|
|
|
|
bool
|
|
PreDispatch(JSContext* aCx) override
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
ClearException ce(aCx);
|
|
JSAutoCompartment ac(aCx, mCallData->mGlobal);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mCallData->mArguments.Length()));
|
|
if (!arguments) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> arg(aCx);
|
|
for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) {
|
|
arg = mCallData->mArguments[i];
|
|
if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
if (!Write(aCx, value)) {
|
|
return false;
|
|
}
|
|
|
|
mCallData->CleanupJSObjects();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) override
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// The windows have to run in parallel.
|
|
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
|
|
|
|
if (aOuterWindow) {
|
|
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
|
|
} else {
|
|
ConsoleStackEntry frame;
|
|
if (mCallData->mTopStackFrame) {
|
|
frame = *mCallData->mTopStackFrame;
|
|
}
|
|
|
|
nsString id;
|
|
if (mWorkerPrivate->IsSharedWorker()) {
|
|
id = NS_LITERAL_STRING("SharedWorker");
|
|
} else if (mWorkerPrivate->IsServiceWorker()) {
|
|
id = NS_LITERAL_STRING("ServiceWorker");
|
|
} else {
|
|
id = NS_LITERAL_STRING("Worker");
|
|
}
|
|
|
|
mCallData->SetIDs(frame.mFilename, id);
|
|
}
|
|
|
|
// Now we could have the correct window (if we are not window-less).
|
|
mClonedData.mParent = aInnerWindow;
|
|
|
|
ProcessCallData(aCx);
|
|
mCallData->CleanupJSObjects();
|
|
|
|
mClonedData.mParent = nullptr;
|
|
}
|
|
|
|
private:
|
|
void
|
|
ProcessCallData(JSContext* aCx)
|
|
{
|
|
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;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < length; ++i) {
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
|
|
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
|
|
return;
|
|
}
|
|
|
|
mCallData->mArguments.AppendElement(value);
|
|
}
|
|
|
|
MOZ_ASSERT(mCallData->mArguments.Length() == length);
|
|
|
|
mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
|
|
mConsole->ProcessCallData(mCallData);
|
|
}
|
|
|
|
nsRefPtr<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*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
if (!global) {
|
|
return false;
|
|
}
|
|
|
|
JSAutoCompartment ac(aCx, global);
|
|
|
|
JS::Rooted<JSObject*> arguments(aCx,
|
|
JS_NewArrayObject(aCx, mArguments.Length()));
|
|
if (!arguments) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> arg(aCx);
|
|
for (uint32_t i = 0; i < mArguments.Length(); ++i) {
|
|
arg = mArguments[i];
|
|
if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
|
|
|
|
if (!Write(aCx, value)) {
|
|
return false;
|
|
}
|
|
|
|
mArguments.Clear();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
|
|
nsPIDOMWindow* aInnerWindow) override
|
|
{
|
|
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);
|
|
}
|
|
|
|
nsString mAction;
|
|
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_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_SCRIPT_OBJECTS
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
|
|
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(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
Console::Console(nsPIDOMWindow* aWindow)
|
|
: mWindow(aWindow)
|
|
, mOuterID(0)
|
|
, mInnerID(0)
|
|
{
|
|
if (mWindow) {
|
|
MOZ_ASSERT(mWindow->IsInnerWindow());
|
|
mInnerID = mWindow->WindowID();
|
|
|
|
nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
|
|
MOZ_ASSERT(outerWindow);
|
|
mOuterID = outerWindow->WindowID();
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->AddObserver(this, "inner-window-destroyed", false);
|
|
}
|
|
}
|
|
|
|
mozilla::HoldJSObjects(this);
|
|
}
|
|
|
|
Console::~Console()
|
|
{
|
|
if (!NS_IsMainThread()) {
|
|
if (mStorage) {
|
|
NS_ReleaseOnMainThread(mStorage);
|
|
}
|
|
|
|
if (mSandbox) {
|
|
NS_ReleaseOnMainThread(mSandbox);
|
|
}
|
|
}
|
|
|
|
mozilla::DropJSObjects(this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Console::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (strcmp(aTopic, "inner-window-destroyed")) {
|
|
return NS_OK;
|
|
}
|
|
|
|
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) {
|
|
nsCOMPtr<nsIObserverService> obs =
|
|
do_GetService("@mozilla.org/observer-service;1");
|
|
if (obs) {
|
|
obs->RemoveObserver(this, "inner-window-destroyed");
|
|
}
|
|
|
|
mTimerRegistry.Clear();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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")
|
|
|
|
void
|
|
Console::Trace(JSContext* aCx)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
Sequence<JS::Value> data;
|
|
SequenceRooter<JS::Value> rooter(aCx, &data);
|
|
|
|
if (aData.isString() && !data.AppendElement(aData, fallible)) {
|
|
return;
|
|
}
|
|
|
|
#ifdef MOZ_ENABLE_PROFILER_SPS
|
|
if (aData.isString() && NS_IsMainThread()) {
|
|
if (!mProfiler) {
|
|
mProfiler = do_GetService("@mozilla.org/tools/profiler;1");
|
|
}
|
|
if (mProfiler) {
|
|
bool active = false;
|
|
if (NS_SUCCEEDED(mProfiler->IsActive(&active)) && active) {
|
|
nsAutoJSString stringValue;
|
|
if (stringValue.init(aCx, aData)) {
|
|
mProfiler->AddMarker(NS_ConvertUTF16toUTF8(stringValue).get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
|
|
}
|
|
|
|
void
|
|
Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
|
|
{
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
|
|
{
|
|
ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
|
|
}
|
|
|
|
void
|
|
Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
|
|
const Sequence<JS::Value>& aData)
|
|
{
|
|
if (!NS_IsMainThread()) {
|
|
// Here we are in a worker thread.
|
|
nsRefPtr<ConsoleProfileRunnable> runnable =
|
|
new ConsoleProfileRunnable(this, aAction, aData);
|
|
runnable->Dispatch();
|
|
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 =
|
|
do_GetService("@mozilla.org/observer-service;1");
|
|
if (obs) {
|
|
obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
|
|
}
|
|
}
|
|
|
|
void
|
|
Console::Assert(JSContext* aCx, bool aCondition,
|
|
const Sequence<JS::Value>& aData)
|
|
{
|
|
if (!aCondition) {
|
|
Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
|
|
}
|
|
}
|
|
|
|
METHOD(Count, "count")
|
|
|
|
void
|
|
Console::NoopMethod()
|
|
{
|
|
// Nothing to do.
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
StackFrameToStackEntry(nsIStackFrame* aStackFrame,
|
|
ConsoleStackEntry& aStackEntry,
|
|
uint32_t aLanguage)
|
|
{
|
|
MOZ_ASSERT(aStackFrame);
|
|
|
|
nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
int32_t lineNumber;
|
|
rv = aStackFrame->GetLineNumber(&lineNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mLineNumber = lineNumber;
|
|
|
|
int32_t columnNumber;
|
|
rv = aStackFrame->GetColumnNumber(&columnNumber);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mColumnNumber = columnNumber;
|
|
|
|
rv = aStackFrame->GetName(aStackEntry.mFunctionName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aStackEntry.mLanguage = aLanguage;
|
|
return NS_OK;
|
|
}
|
|
|
|
static
|
|
nsresult
|
|
ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
|
|
{
|
|
nsCOMPtr<nsIStackFrame> stack(aStack);
|
|
|
|
while (stack) {
|
|
uint32_t language;
|
|
nsresult rv = stack->GetLanguage(&language);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (language == nsIProgrammingLanguage::JAVASCRIPT) {
|
|
ConsoleStackEntry& data = *aRefiedStack.AppendElement();
|
|
rv = StackFrameToStackEntry(stack, data, language);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> caller;
|
|
rv = stack->GetCaller(getter_AddRefs(caller));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
stack.swap(caller);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class ConsoleTimelineMarker : public TimelineMarker
|
|
{
|
|
public:
|
|
ConsoleTimelineMarker(nsDocShell* aDocShell,
|
|
TracingMetadata aMetaData,
|
|
const nsAString& aCause)
|
|
: TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
|
|
{
|
|
if (aMetaData == TRACING_INTERVAL_END) {
|
|
CaptureStack();
|
|
}
|
|
}
|
|
|
|
virtual bool Equals(const TimelineMarker& aOther) override
|
|
{
|
|
if (!TimelineMarker::Equals(aOther)) {
|
|
return false;
|
|
}
|
|
// Console markers must have matching causes as well.
|
|
return GetCause() == aOther.GetCause();
|
|
}
|
|
|
|
virtual void AddDetails(JSContext* aCx,
|
|
mozilla::dom::ProfileTimelineMarker& aMarker) override
|
|
{
|
|
if (GetMetaData() == TRACING_INTERVAL_START) {
|
|
aMarker.mCauseName.Construct(GetCause());
|
|
} else {
|
|
aMarker.mEndStack = GetStack();
|
|
}
|
|
}
|
|
};
|
|
|
|
class TimestampTimelineMarker : public TimelineMarker
|
|
{
|
|
public:
|
|
TimestampTimelineMarker(nsDocShell* aDocShell,
|
|
TracingMetadata aMetaData,
|
|
const nsAString& aCause)
|
|
: TimelineMarker(aDocShell, "TimeStamp", aMetaData, aCause)
|
|
{
|
|
MOZ_ASSERT(aMetaData == TRACING_TIMESTAMP);
|
|
}
|
|
|
|
virtual void AddDetails(JSContext* aCx,
|
|
mozilla::dom::ProfileTimelineMarker& aMarker) override
|
|
{
|
|
if (!GetCause().IsEmpty()) {
|
|
aMarker.mCauseName.Construct(GetCause());
|
|
}
|
|
}
|
|
};
|
|
|
|
// 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)
|
|
{
|
|
nsRefPtr<ConsoleCallData> callData(new ConsoleCallData());
|
|
|
|
ClearException ce(aCx);
|
|
|
|
callData->Initialize(aCx, aMethodName, aMethodString, aData);
|
|
|
|
if (mWindow) {
|
|
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
|
|
if (!webNav) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
|
|
MOZ_ASSERT(loadContext);
|
|
|
|
loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
|
|
}
|
|
|
|
uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ?
|
|
DEFAULT_MAX_STACKTRACE_DEPTH : 1;
|
|
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
|
|
|
|
if (!stack) {
|
|
return;
|
|
}
|
|
|
|
// Walk up to the first JS stack frame and save it if we find it.
|
|
do {
|
|
uint32_t language;
|
|
nsresult rv = stack->GetLanguage(&language);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
if (language == nsIProgrammingLanguage::JAVASCRIPT) {
|
|
callData->mTopStackFrame.emplace();
|
|
nsresult rv = StackFrameToStackEntry(stack,
|
|
*callData->mTopStackFrame,
|
|
language);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> caller;
|
|
rv = stack->GetCaller(getter_AddRefs(caller));
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
stack.swap(caller);
|
|
} while (stack);
|
|
|
|
if (NS_IsMainThread()) {
|
|
callData->mStack = stack;
|
|
} else {
|
|
// nsIStackFrame is not threadsafe, so we need to snapshot it now,
|
|
// before we post our runnable to the main thread.
|
|
callData->mReifiedStack.emplace();
|
|
nsresult rv = ReifyStack(stack, *callData->mReifiedStack);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
nsRefPtr<nsPerformance> performance = win->GetPerformance();
|
|
if (!performance) {
|
|
return;
|
|
}
|
|
|
|
callData->mMonotonicTimer = performance->Now();
|
|
|
|
// 'time' and 'timeEnd' are displayed in the devtools timeline if active.
|
|
bool isTimelineRecording = false;
|
|
nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
|
|
if (docShell) {
|
|
docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
|
|
}
|
|
|
|
// '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);
|
|
}
|
|
|
|
mozilla::UniquePtr<TimelineMarker> marker =
|
|
MakeUnique<TimestampTimelineMarker>(docShell, TRACING_TIMESTAMP, key);
|
|
TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
|
|
}
|
|
// 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)) {
|
|
mozilla::UniquePtr<TimelineMarker> marker =
|
|
MakeUnique<ConsoleTimelineMarker>(docShell,
|
|
aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
|
|
key);
|
|
TimelineConsumers::AddMarkerForDocShell(docShell, Move(marker));
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
|
MOZ_ASSERT(workerPrivate);
|
|
|
|
TimeDuration duration =
|
|
mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
|
|
|
|
callData->mMonotonicTimer = duration.ToMilliseconds();
|
|
}
|
|
}
|
|
|
|
if (NS_IsMainThread()) {
|
|
callData->SetIDs(mOuterID, mInnerID);
|
|
ProcessCallData(callData);
|
|
return;
|
|
}
|
|
|
|
nsRefPtr<ConsoleCallDataRunnable> runnable =
|
|
new ConsoleCallDataRunnable(this, callData);
|
|
runnable->Dispatch();
|
|
}
|
|
|
|
// We store information to lazily compute the stack in the reserved slots of
|
|
// LazyStackGetter. The first slot always stores a JS object: it's either the
|
|
// JS wrapper of the nsIStackFrame or the actual reified stack representation.
|
|
// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
|
|
// reified the stack yet, or an UndefinedValue() otherwise.
|
|
enum {
|
|
SLOT_STACKOBJ,
|
|
SLOT_RAW_STACK
|
|
};
|
|
|
|
bool
|
|
LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
|
|
{
|
|
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
|
|
JS::Rooted<JSObject*> callee(aCx, &args.callee());
|
|
|
|
JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
|
|
if (v.isUndefined()) {
|
|
// Already reified.
|
|
args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
|
|
return true;
|
|
}
|
|
|
|
nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
|
|
nsTArray<ConsoleStackEntry> reifiedStack;
|
|
nsresult rv = ReifyStack(stack, reifiedStack);
|
|
if (NS_FAILED(rv)) {
|
|
Throw(aCx, rv);
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> stackVal(aCx);
|
|
if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(stackVal.isObject());
|
|
|
|
js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
|
|
js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
|
|
|
|
args.rval().set(stackVal);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Console::ProcessCallData(ConsoleCallData* aData)
|
|
{
|
|
MOZ_ASSERT(aData);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
ConsoleStackEntry frame;
|
|
if (aData->mTopStackFrame) {
|
|
frame = *aData->mTopStackFrame;
|
|
}
|
|
|
|
AutoSafeJSContext cx;
|
|
ClearException ce(cx);
|
|
RootedDictionary<ConsoleEvent> event(cx);
|
|
|
|
JSAutoCompartment ac(cx, aData->mGlobal);
|
|
|
|
event.mID.Construct();
|
|
event.mInnerID.Construct();
|
|
|
|
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
|
|
if (aData->mIDType == ConsoleCallData::eString) {
|
|
event.mID.Value().SetAsString() = aData->mOuterIDString;
|
|
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
|
|
} else {
|
|
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
|
|
event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
|
|
event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
|
|
}
|
|
|
|
event.mLevel = aData->mMethodString;
|
|
event.mFilename = frame.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 (!ProcessArguments(cx, aData->mArguments, event.mArguments.Value(),
|
|
event.mStyles.Value())) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
event.mArguments.Construct();
|
|
if (!ArgumentsToValueList(aData->mArguments, event.mArguments.Value())) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (aData->mMethodName == MethodGroup ||
|
|
aData->mMethodName == MethodGroupCollapsed ||
|
|
aData->mMethodName == MethodGroupEnd) {
|
|
ComposeGroupName(cx, aData->mArguments, event.mGroupName);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTime && !aData->mArguments.IsEmpty()) {
|
|
event.mTimer = StartTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodTimeEnd && !aData->mArguments.IsEmpty()) {
|
|
event.mTimer = StopTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
|
|
}
|
|
|
|
else if (aData->mMethodName == MethodCount) {
|
|
event.mCounter = IncreaseCounter(cx, frame, aData->mArguments);
|
|
}
|
|
|
|
// We want to create a console event object and pass it to our
|
|
// nsIConsoleAPIStorage implementation. We want to define some accessor
|
|
// properties on this object, and those will need to keep an nsIStackFrame
|
|
// alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
|
|
// further, passing untrusted objects to system code is likely to run afoul of
|
|
// Object Xrays. So we want to wrap in a system-principal scope here. But
|
|
// which one? We could cheat and try to get the underlying JSObject* of
|
|
// mStorage, but that's a bit fragile. Instead, we just use the junk scope,
|
|
// with explicit permission from the XPConnect module owner. If you're
|
|
// tempted to do that anywhere else, talk to said module owner first.
|
|
JSAutoCompartment ac2(cx, xpc::PrivilegedJunkScope());
|
|
|
|
JS::Rooted<JS::Value> eventValue(cx);
|
|
if (!ToJSValue(cx, event, &eventValue)) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
|
|
MOZ_ASSERT(eventObj);
|
|
|
|
if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
|
|
return;
|
|
}
|
|
|
|
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(cx);
|
|
if (!ToJSValue(cx, *aData->mReifiedStack, &stacktrace) ||
|
|
!JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
|
|
JSPROP_ENUMERATE)) {
|
|
return;
|
|
}
|
|
} else {
|
|
JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
|
|
"stacktrace");
|
|
if (!fun) {
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
|
|
|
|
// We want to store our stack in the function and have it stay alive. But
|
|
// we also need sane access to the C++ nsIStackFrame. So store both a JS
|
|
// wrapper and the raw pointer: the former will keep the latter alive.
|
|
JS::Rooted<JS::Value> stackVal(cx);
|
|
nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
|
|
&stackVal);
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
|
|
js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
|
|
JS::PrivateValue(aData->mStack.get()));
|
|
|
|
if (!JS_DefineProperty(cx, eventObj, "stacktrace",
|
|
JS::UndefinedHandleValue,
|
|
JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
|
|
JSPROP_SETTER,
|
|
JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
|
|
nullptr)) {
|
|
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 (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
|
|
NS_WARNING("Failed to record a console event.");
|
|
}
|
|
}
|
|
|
|
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 (!str) {
|
|
return false;
|
|
}
|
|
|
|
if (!aSequence.AppendElement(JS::StringValue(str), fallible)) {
|
|
return false;
|
|
}
|
|
|
|
aOutput.Truncate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
bool
|
|
Console::ProcessArguments(JSContext* aCx,
|
|
const nsTArray<JS::Heap<JS::Value>>& aData,
|
|
Sequence<JS::Value>& aSequence,
|
|
Sequence<JS::Value>& aStyles)
|
|
{
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString string;
|
|
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 (!FlushOutput(aCx, aSequence, output)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> v(aCx);
|
|
if (index < aData.Length()) {
|
|
v = aData[index++];
|
|
}
|
|
|
|
if (!aSequence.AppendElement(v, fallible)) {
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'c':
|
|
{
|
|
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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
int32_t diff = aSequence.Length() - aStyles.Length();
|
|
if (diff > 0) {
|
|
for (int32_t i = 0; i < diff; i++) {
|
|
if (!aStyles.AppendElement(JS::NullValue(), fallible)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aStyles.AppendElement(JS::StringValue(jsString), 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 (!jsString) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoJSString v;
|
|
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 (!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 (!JS::ToNumber(aCx, value, &v)) {
|
|
return false;
|
|
}
|
|
|
|
nsCString format;
|
|
MakeFormatString(format, integer, mantissa, 'f');
|
|
output.AppendPrintf(format.get(), v);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
output.Append(tmp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 (!aSequence.AppendElement(aData[index], fallible)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
|
|
int32_t aMantissa, char aCh)
|
|
{
|
|
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 nsTArray<JS::Heap<JS::Value>>& aData,
|
|
nsAString& aName)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
JS::Value
|
|
Console::StartTimer(JSContext* aCx, const JS::Value& aName,
|
|
DOMHighResTimeStamp aTimestamp)
|
|
{
|
|
if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
|
|
RootedDictionary<ConsoleTimerError> error(aCx);
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, error, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
RootedDictionary<ConsoleTimerStart> timer(aCx);
|
|
|
|
JS::Rooted<JS::Value> name(aCx, aName);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
|
if (!jsString) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (!key.init(aCx, jsString)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
timer.mName = key;
|
|
|
|
DOMHighResTimeStamp entry;
|
|
if (!mTimerRegistry.Get(key, &entry)) {
|
|
mTimerRegistry.Put(key, aTimestamp);
|
|
} else {
|
|
aTimestamp = entry;
|
|
}
|
|
|
|
timer.mStarted = aTimestamp;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, timer, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
JS::Value
|
|
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
|
|
DOMHighResTimeStamp aTimestamp)
|
|
{
|
|
JS::Rooted<JS::Value> name(aCx, aName);
|
|
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
|
|
if (!jsString) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
nsAutoJSString key;
|
|
if (!key.init(aCx, jsString)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
DOMHighResTimeStamp entry;
|
|
if (!mTimerRegistry.Get(key, &entry)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
mTimerRegistry.Remove(key);
|
|
|
|
RootedDictionary<ConsoleTimerEnd> timer(aCx);
|
|
timer.mName = key;
|
|
timer.mDuration = aTimestamp - entry;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, timer, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool
|
|
Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
|
|
Sequence<JS::Value>& aSequence)
|
|
{
|
|
for (uint32_t i = 0; i < aData.Length(); ++i) {
|
|
if (!aSequence.AppendElement(aData[i], fallible)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
JS::Value
|
|
Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
|
|
const nsTArray<JS::Heap<JS::Value>>& aArguments)
|
|
{
|
|
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)) {
|
|
if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
|
|
RootedDictionary<ConsoleCounterError> error(aCx);
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, error, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
}
|
|
|
|
++count;
|
|
mCounterRegistry.Put(key, count);
|
|
|
|
RootedDictionary<ConsoleCounter> data(aCx);
|
|
data.mLabel = label;
|
|
data.mCount = count;
|
|
|
|
JS::Rooted<JS::Value> value(aCx);
|
|
if (!ToJSValue(aCx, data, &value)) {
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool
|
|
Console::ShouldIncludeStackTrace(MethodName aMethodName)
|
|
{
|
|
switch (aMethodName) {
|
|
case MethodError:
|
|
case MethodException:
|
|
case MethodAssert:
|
|
case MethodTrace:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
JSObject*
|
|
Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
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();
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|