Mypal/js/xpconnect/src/nsXPConnect.cpp

1421 lines
42 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* 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/. */
/* High level class and public functions implementation. */
#include "mozilla/Assertions.h"
#include "mozilla/Base64.h"
#include "mozilla/Likely.h"
#include "mozilla/Unused.h"
#include "xpcprivate.h"
#include "XPCWrapper.h"
#include "jsfriendapi.h"
#include "nsJSEnvironment.h"
#include "nsThreadUtils.h"
#include "nsDOMJSUtils.h"
#include "WrapperFactory.h"
#include "AccessCheck.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/Promise.h"
#include "nsDOMMutationObserver.h"
#include "nsICycleCollectorListener.h"
#include "mozilla/XPTInterfaceInfoManager.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsScriptSecurityManager.h"
#include "nsIPermissionManager.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsScriptError.h"
#include "jsfriendapi.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace xpc;
using namespace JS;
NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect)
nsXPConnect* nsXPConnect::gSelf = nullptr;
bool nsXPConnect::gOnceAliveNowDead = false;
// Global cache of the default script security manager (QI'd to
// nsIScriptSecurityManager) and the system principal.
nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr;
nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr;
const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1";
const char XPC_EXCEPTION_CONTRACTID[] = "@mozilla.org/js/xpc/Exception;1";
const char XPC_CONSOLE_CONTRACTID[] = "@mozilla.org/consoleservice;1";
const char XPC_SCRIPT_ERROR_CONTRACTID[] = "@mozilla.org/scripterror;1";
const char XPC_ID_CONTRACTID[] = "@mozilla.org/js/xpc/ID;1";
const char XPC_XPCONNECT_CONTRACTID[] = "@mozilla.org/js/xpc/XPConnect;1";
/***************************************************************************/
nsXPConnect::nsXPConnect()
: mContext(nullptr),
mShuttingDown(false)
{
mContext = XPCJSContext::newXPCJSContext();
if (!mContext) {
NS_RUNTIMEABORT("Couldn't create XPCJSContext.");
}
}
nsXPConnect::~nsXPConnect()
{
mContext->DeleteSingletonScopes();
// In order to clean up everything properly, we need to GC twice: once now,
// to clean anything that can go away on its own (like the Junk Scope, which
// we unrooted above), and once after forcing a bunch of shutdown in
// XPConnect, to clean the stuff we forcibly disconnected. The forced
// shutdown code defaults to leaking in a number of situations, so we can't
// get by with only the second GC. :-(
mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN);
mShuttingDown = true;
XPCWrappedNativeScope::SystemIsBeingShutDown();
// The above causes us to clean up a bunch of XPConnect data structures,
// after which point we need to GC to clean everything up. We need to do
// this before deleting the XPCJSContext, because doing so destroys the
// maps that our finalize callback depends on.
mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN);
NS_RELEASE(gSystemPrincipal);
gScriptSecurityManager = nullptr;
// shutdown the logging system
XPC_LOG_FINISH();
delete mContext;
gSelf = nullptr;
gOnceAliveNowDead = true;
}
// static
void
nsXPConnect::InitStatics()
{
gSelf = new nsXPConnect();
gOnceAliveNowDead = false;
if (!gSelf->mContext) {
NS_RUNTIMEABORT("Couldn't create XPCJSContext.");
}
// Initial extra ref to keep the singleton alive
// balanced by explicit call to ReleaseXPConnectSingleton()
NS_ADDREF(gSelf);
// Fire up the SSM.
nsScriptSecurityManager::InitStatics();
gScriptSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager();
gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
MOZ_RELEASE_ASSERT(gSystemPrincipal);
if (!JS::InitSelfHostedCode(gSelf->mContext->Context()))
MOZ_CRASH("InitSelfHostedCode failed");
if (!gSelf->mContext->JSContextInitialized(gSelf->mContext->Context()))
MOZ_CRASH("JSContextInitialized failed");
// Initialize our singleton scopes.
gSelf->mContext->InitSingletonScopes();
}
nsXPConnect*
nsXPConnect::GetSingleton()
{
nsXPConnect* xpc = nsXPConnect::XPConnect();
NS_IF_ADDREF(xpc);
return xpc;
}
// static
void
nsXPConnect::ReleaseXPConnectSingleton()
{
nsXPConnect* xpc = gSelf;
if (xpc) {
nsrefcnt cnt;
NS_RELEASE2(xpc, cnt);
}
}
// static
XPCJSContext*
nsXPConnect::GetContextInstance()
{
nsXPConnect* xpc = XPConnect();
return xpc->GetContext();
}
// static
bool
nsXPConnect::IsISupportsDescendant(nsIInterfaceInfo* info)
{
bool found = false;
if (info)
info->HasAncestor(&NS_GET_IID(nsISupports), &found);
return found;
}
void
xpc::ErrorBase::Init(JSErrorBase* aReport)
{
if (!aReport->filename) {
mFileName.SetIsVoid(true);
} else {
mFileName.AssignWithConversion(aReport->filename);
}
mLineNumber = aReport->lineno;
mColumn = aReport->column;
}
void
xpc::ErrorNote::Init(JSErrorNotes::Note* aNote)
{
xpc::ErrorBase::Init(aNote);
ErrorNoteToMessageString(aNote, mErrorMsg);
}
void
xpc::ErrorReport::Init(JSErrorReport* aReport, const char* aToStringResult,
bool aIsChrome, uint64_t aWindowID)
{
xpc::ErrorBase::Init(aReport);
mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript")
: NS_LITERAL_CSTRING("content javascript");
mWindowID = aWindowID;
ErrorReportToMessageString(aReport, mErrorMsg);
if (mErrorMsg.IsEmpty() && aToStringResult) {
AppendUTF8toUTF16(aToStringResult, mErrorMsg);
}
mSourceLine.Assign(aReport->linebuf(), aReport->linebufLength());
const JSErrorFormatString* efs = js::GetErrorMessage(nullptr, aReport->errorNumber);
if (efs == nullptr) {
mErrorMsgName.AssignASCII("");
} else {
mErrorMsgName.AssignASCII(efs->name);
}
mFlags = aReport->flags;
mIsMuted = aReport->isMuted;
if (aReport->notes) {
if (!mNotes.SetLength(aReport->notes->length(), fallible)) {
return;
}
size_t i = 0;
for (auto&& note : *aReport->notes) {
mNotes.ElementAt(i).Init(note.get());
i++;
}
}
}
void
xpc::ErrorReport::Init(JSContext* aCx, mozilla::dom::Exception* aException,
bool aIsChrome, uint64_t aWindowID)
{
mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript")
: NS_LITERAL_CSTRING("content javascript");
mWindowID = aWindowID;
aException->GetErrorMessage(mErrorMsg);
aException->GetFilename(aCx, mFileName);
if (mFileName.IsEmpty()) {
mFileName.SetIsVoid(true);
}
aException->GetLineNumber(aCx, &mLineNumber);
aException->GetColumnNumber(&mColumn);
mFlags = JSREPORT_EXCEPTION;
}
static LazyLogModule gJSDiagnostics("JSDiagnostics");
void
xpc::ErrorBase::AppendErrorDetailsTo(nsCString& error)
{
error.Append(NS_LossyConvertUTF16toASCII(mFileName));
error.AppendLiteral(", line ");
error.AppendInt(mLineNumber, 10);
error.AppendLiteral(": ");
error.Append(NS_LossyConvertUTF16toASCII(mErrorMsg));
}
void
xpc::ErrorNote::LogToStderr()
{
if (!nsContentUtils::DOMWindowDumpEnabled()) {
return;
}
nsAutoCString error;
error.AssignLiteral("JavaScript note: ");
AppendErrorDetailsTo(error);
fprintf(stderr, "%s\n", error.get());
fflush(stderr);
}
void
xpc::ErrorReport::LogToStderr()
{
if (!nsContentUtils::DOMWindowDumpEnabled()) {
return;
}
nsAutoCString error;
error.AssignLiteral("JavaScript ");
if (JSREPORT_IS_STRICT(mFlags)) {
error.AppendLiteral("strict ");
}
if (JSREPORT_IS_WARNING(mFlags)) {
error.AppendLiteral("warning: ");
} else {
error.AppendLiteral("error: ");
}
AppendErrorDetailsTo(error);
fprintf(stderr, "%s\n", error.get());
fflush(stderr);
for (size_t i = 0, len = mNotes.Length(); i < len; i++) {
ErrorNote& note = mNotes[i];
note.LogToStderr();
}
}
void
xpc::ErrorReport::LogToConsole()
{
LogToConsoleWithStack(nullptr);
}
void
xpc::ErrorReport::LogToConsoleWithStack(JS::HandleObject aStack)
{
LogToStderr();
MOZ_LOG(gJSDiagnostics,
JSREPORT_IS_WARNING(mFlags) ? LogLevel::Warning : LogLevel::Error,
("file %s, line %u\n%s", NS_LossyConvertUTF16toASCII(mFileName).get(),
mLineNumber, NS_LossyConvertUTF16toASCII(mErrorMsg).get()));
// Log to the console. We do this last so that we can simply return if
// there's no console service without affecting the other reporting
// mechanisms.
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(consoleService);
RefPtr<nsScriptErrorBase> errorObject;
if (mWindowID && aStack) {
// Only set stack on messages related to a document
// As we cache messages in the console service,
// we have to ensure not leaking them after the related
// context is destroyed and we only track document lifecycle for now.
errorObject = new nsScriptErrorWithStack(aStack);
} else {
errorObject = new nsScriptError();
}
errorObject->SetErrorMessageName(mErrorMsgName);
nsresult rv = errorObject->InitWithWindowID(mErrorMsg, mFileName, mSourceLine,
mLineNumber, mColumn, mFlags,
mCategory, mWindowID);
NS_ENSURE_SUCCESS_VOID(rv);
for (size_t i = 0, len = mNotes.Length(); i < len; i++) {
ErrorNote& note = mNotes[i];
nsScriptErrorNote* noteObject = new nsScriptErrorNote();
noteObject->Init(note.mErrorMsg, note.mFileName,
note.mLineNumber, note.mColumn);
errorObject->AddNote(noteObject);
}
consoleService->LogMessage(errorObject);
}
/* static */
void
xpc::ErrorNote::ErrorNoteToMessageString(JSErrorNotes::Note* aNote,
nsAString& aString)
{
aString.Truncate();
if (aNote->message()) {
aString.Append(NS_ConvertUTF8toUTF16(aNote->message().c_str()));
}
}
/* static */
void
xpc::ErrorReport::ErrorReportToMessageString(JSErrorReport* aReport,
nsAString& aString)
{
aString.Truncate();
if (aReport->message()) {
JSFlatString* name = js::GetErrorTypeName(CycleCollectedJSContext::Get()->Context(), aReport->exnType);
if (name) {
AssignJSFlatString(aString, name);
aString.AppendLiteral(": ");
}
aString.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
}
}
/***************************************************************************/
nsresult
nsXPConnect::GetInfoForIID(const nsIID * aIID, nsIInterfaceInfo** info)
{
return XPTInterfaceInfoManager::GetSingleton()->GetInfoForIID(aIID, info);
}
nsresult
nsXPConnect::GetInfoForName(const char * name, nsIInterfaceInfo** info)
{
nsresult rv = XPTInterfaceInfoManager::GetSingleton()->GetInfoForName(name, info);
return NS_FAILED(rv) ? NS_OK : NS_ERROR_NO_INTERFACE;
}
NS_IMETHODIMP
nsXPConnect::GarbageCollect(uint32_t reason)
{
GetContext()->GarbageCollect(reason);
return NS_OK;
}
void
xpc_MarkInCCGeneration(nsISupports* aVariant, uint32_t aGeneration)
{
nsCOMPtr<XPCVariant> variant = do_QueryInterface(aVariant);
if (variant) {
variant->SetCCGeneration(aGeneration);
variant->GetJSVal(); // Unmarks gray JSObject.
XPCVariant* weak = variant.get();
variant = nullptr;
if (weak->IsPurple()) {
weak->RemovePurple();
}
}
}
void
xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS)
{
// QIing to nsIXPConnectWrappedJSUnmarkGray may have side effects!
nsCOMPtr<nsIXPConnectWrappedJSUnmarkGray> wjsug =
do_QueryInterface(aWrappedJS);
Unused << wjsug;
MOZ_ASSERT(!wjsug, "One should never be able to QI to "
"nsIXPConnectWrappedJSUnmarkGray successfully!");
}
/***************************************************************************/
/***************************************************************************/
// nsIXPConnect interface methods...
template<typename T>
static inline T UnexpectedFailure(T rv)
{
NS_ERROR("This is not supposed to fail!");
return rv;
}
void
xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj)
{
if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL)
mozilla::dom::TraceProtoAndIfaceCache(trc, obj);
// We might be called from a GC during the creation of a global, before we've
// been able to set up the compartment private or the XPCWrappedNativeScope,
// so we need to null-check those.
xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(obj);
if (compartmentPrivate && compartmentPrivate->scope)
compartmentPrivate->scope->TraceInside(trc);
}
namespace xpc {
JSObject*
CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal,
JS::CompartmentOptions& aOptions)
{
MOZ_ASSERT(NS_IsMainThread(), "using a principal off the main thread?");
MOZ_ASSERT(principal);
MOZ_RELEASE_ASSERT(principal != nsContentUtils::GetNullSubjectPrincipal(),
"The null subject principal is getting inherited - fix that!");
RootedObject global(cx,
JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal),
JS::DontFireOnNewGlobalHook, aOptions));
if (!global)
return nullptr;
JSAutoCompartment ac(cx, global);
// The constructor automatically attaches the scope to the compartment private
// of |global|.
(void) new XPCWrappedNativeScope(cx, global);
if (clasp->flags & JSCLASS_DOM_GLOBAL) {
#ifdef DEBUG
// Verify that the right trace hook is called. Note that this doesn't
// work right for wrapped globals, since the tracing situation there is
// more complicated. Manual inspection shows that they do the right
// thing. Also note that we only check this for JSCLASS_DOM_GLOBAL
// classes because xpc::TraceXPCGlobal won't call
// TraceProtoAndIfaceCache unless that flag is set.
if (!((const js::Class*)clasp)->isWrappedNative())
{
VerifyTraceProtoAndIfaceCacheCalledTracer trc(cx);
TraceChildren(&trc, GCCellPtr(global.get()));
MOZ_ASSERT(trc.ok, "Trace hook on global needs to call TraceXPCGlobal for XPConnect compartments.");
}
#endif
const char* className = clasp->name;
AllocateProtoAndIfaceCache(global,
(strcmp(className, "Window") == 0 ||
strcmp(className, "ChromeWindow") == 0)
? ProtoAndIfaceCache::WindowLike
: ProtoAndIfaceCache::NonWindowLike);
}
return global;
}
void
InitGlobalObjectOptions(JS::CompartmentOptions& aOptions,
nsIPrincipal* aPrincipal)
{
bool shouldDiscardSystemSource = ShouldDiscardSystemSource();
bool extraWarningsForSystemJS = ExtraWarningsForSystemJS();
bool isSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
if (isSystem) {
// Make sure [SecureContext] APIs are visible:
aOptions.creationOptions().setSecureContext(true);
}
if (shouldDiscardSystemSource) {
bool discardSource = isSystem;
aOptions.behaviors().setDiscardSource(discardSource);
}
if (extraWarningsForSystemJS) {
if (isSystem)
aOptions.behaviors().extraWarningsOverride().set(true);
}
}
bool
InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal, uint32_t aFlags)
{
// Immediately enter the global's compartment so that everything we create
// ends up there.
JSAutoCompartment ac(aJSContext, aGlobal);
// Stuff coming through this path always ends up as a DOM global.
MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL);
if (!(aFlags & nsIXPConnect::OMIT_COMPONENTS_OBJECT)) {
// XPCCallContext gives us an active request needed to save/restore.
if (!CompartmentPrivate::Get(aGlobal)->scope->AttachComponentsObject(aJSContext) ||
!XPCNativeWrapper::AttachNewConstructorObject(aJSContext, aGlobal)) {
return UnexpectedFailure(false);
}
}
if (!(aFlags & nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK))
JS_FireOnNewGlobalObject(aJSContext, aGlobal);
return true;
}
} // namespace xpc
NS_IMETHODIMP
nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext,
nsISupports* aCOMObj,
nsIPrincipal * aPrincipal,
uint32_t aFlags,
JS::CompartmentOptions& aOptions,
nsIXPConnectJSObjectHolder** _retval)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
MOZ_ASSERT(_retval, "bad param");
// We pass null for the 'extra' pointer during global object creation, so
// we need to have a principal.
MOZ_ASSERT(aPrincipal);
InitGlobalObjectOptions(aOptions, aPrincipal);
// Call into XPCWrappedNative to make a new global object, scope, and global
// prototype.
xpcObjectHelper helper(aCOMObj);
MOZ_ASSERT(helper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT);
RefPtr<XPCWrappedNative> wrappedGlobal;
nsresult rv =
XPCWrappedNative::WrapNewGlobal(helper, aPrincipal,
aFlags & nsIXPConnect::INIT_JS_STANDARD_CLASSES,
aOptions, getter_AddRefs(wrappedGlobal));
NS_ENSURE_SUCCESS(rv, rv);
// Grab a copy of the global and enter its compartment.
RootedObject global(aJSContext, wrappedGlobal->GetFlatJSObject());
MOZ_ASSERT(JS_IsGlobalObject(global));
if (!InitGlobalObject(aJSContext, global, aFlags))
return UnexpectedFailure(NS_ERROR_FAILURE);
wrappedGlobal.forget(_retval);
return NS_OK;
}
static nsresult
NativeInterface2JSObject(HandleObject aScope,
nsISupports* aCOMObj,
nsWrapperCache* aCache,
const nsIID * aIID,
bool aAllowWrapping,
MutableHandleValue aVal,
nsIXPConnectJSObjectHolder** aHolder)
{
AutoJSContext cx;
JSAutoCompartment ac(cx, aScope);
nsresult rv;
xpcObjectHelper helper(aCOMObj, aCache);
if (!XPCConvert::NativeInterface2JSObject(aVal, aHolder, helper, aIID,
aAllowWrapping, &rv))
return rv;
MOZ_ASSERT(aAllowWrapping || !xpc::WrapperFactory::IsXrayWrapper(&aVal.toObject()),
"Shouldn't be returning a xray wrapper here");
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::WrapNative(JSContext * aJSContext,
JSObject * aScopeArg,
nsISupports* aCOMObj,
const nsIID & aIID,
JSObject** aRetVal)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aScopeArg, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
RootedObject aScope(aJSContext, aScopeArg);
RootedValue v(aJSContext);
nsresult rv = NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
true, &v, nullptr);
if (NS_FAILED(rv))
return rv;
if (!v.isObjectOrNull())
return NS_ERROR_FAILURE;
*aRetVal = v.toObjectOrNull();
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::WrapNativeHolder(JSContext * aJSContext,
JSObject * aScopeArg,
nsISupports* aCOMObj,
const nsIID & aIID,
nsIXPConnectJSObjectHolder **aHolder)
{
MOZ_ASSERT(aHolder, "bad param");
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aScopeArg, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
RootedObject aScope(aJSContext, aScopeArg);
RootedValue v(aJSContext);
return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID,
true, &v, aHolder);
}
NS_IMETHODIMP
nsXPConnect::WrapNativeToJSVal(JSContext* aJSContext,
JSObject* aScopeArg,
nsISupports* aCOMObj,
nsWrapperCache* aCache,
const nsIID* aIID,
bool aAllowWrapping,
MutableHandleValue aVal)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aScopeArg, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
RootedObject aScope(aJSContext, aScopeArg);
return NativeInterface2JSObject(aScope, aCOMObj, aCache, aIID,
aAllowWrapping, aVal, nullptr);
}
NS_IMETHODIMP
nsXPConnect::WrapJS(JSContext * aJSContext,
JSObject * aJSObjArg,
const nsIID & aIID,
void * *result)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aJSObjArg, "bad param");
MOZ_ASSERT(result, "bad param");
*result = nullptr;
RootedObject aJSObj(aJSContext, aJSObjArg);
JSAutoCompartment ac(aJSContext, aJSObj);
nsresult rv = NS_ERROR_UNEXPECTED;
if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
&aIID, nullptr, &rv))
return rv;
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::JSValToVariant(JSContext* cx,
HandleValue aJSVal,
nsIVariant** aResult)
{
NS_PRECONDITION(aResult, "bad param");
RefPtr<XPCVariant> variant = XPCVariant::newVariant(cx, aJSVal);
variant.forget(aResult);
NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::WrapJSAggregatedToNative(nsISupports* aOuter,
JSContext* aJSContext,
JSObject* aJSObjArg,
const nsIID& aIID,
void** result)
{
MOZ_ASSERT(aOuter, "bad param");
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aJSObjArg, "bad param");
MOZ_ASSERT(result, "bad param");
*result = nullptr;
RootedObject aJSObj(aJSContext, aJSObjArg);
nsresult rv;
if (!XPCConvert::JSObject2NativeInterface(result, aJSObj,
&aIID, aOuter, &rv))
return rv;
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::GetWrappedNativeOfJSObject(JSContext * aJSContext,
JSObject * aJSObjArg,
nsIXPConnectWrappedNative** _retval)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aJSObjArg, "bad param");
MOZ_ASSERT(_retval, "bad param");
RootedObject aJSObj(aJSContext, aJSObjArg);
aJSObj = js::CheckedUnwrap(aJSObj, /* stopAtWindowProxy = */ false);
if (!aJSObj || !IS_WN_REFLECTOR(aJSObj)) {
*_retval = nullptr;
return NS_ERROR_FAILURE;
}
RefPtr<XPCWrappedNative> temp = XPCWrappedNative::Get(aJSObj);
temp.forget(_retval);
return NS_OK;
}
already_AddRefed<nsISupports>
xpc::UnwrapReflectorToISupports(JSObject* reflector)
{
// Unwrap security wrappers, if allowed.
reflector = js::CheckedUnwrap(reflector, /* stopAtWindowProxy = */ false);
if (!reflector)
return nullptr;
// Try XPCWrappedNatives.
if (IS_WN_REFLECTOR(reflector)) {
XPCWrappedNative* wn = XPCWrappedNative::Get(reflector);
if (!wn)
return nullptr;
nsCOMPtr<nsISupports> native = wn->Native();
return native.forget();
}
// Try DOM objects. This QI without taking a ref first is safe, because
// this if non-null our thing will definitely be a DOM object, and we know
// their QI to nsISupports doesn't do anything weird.
nsCOMPtr<nsISupports> canonical =
do_QueryInterface(mozilla::dom::UnwrapDOMObjectToISupports(reflector));
return canonical.forget();
}
NS_IMETHODIMP
nsXPConnect::GetWrappedNativeOfNativeObject(JSContext * aJSContext,
JSObject * aScopeArg,
nsISupports* aCOMObj,
const nsIID & aIID,
nsIXPConnectWrappedNative** _retval)
{
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aScopeArg, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
MOZ_ASSERT(_retval, "bad param");
*_retval = nullptr;
RootedObject aScope(aJSContext, aScopeArg);
XPCWrappedNativeScope* scope = ObjectScope(aScope);
if (!scope)
return UnexpectedFailure(NS_ERROR_FAILURE);
RefPtr<XPCNativeInterface> iface =
XPCNativeInterface::GetNewOrUsed(&aIID);
if (!iface)
return NS_ERROR_FAILURE;
XPCWrappedNative* wrapper;
nsresult rv = XPCWrappedNative::GetUsedOnly(aCOMObj, scope, iface, &wrapper);
if (NS_FAILED(rv))
return NS_ERROR_FAILURE;
*_retval = static_cast<nsIXPConnectWrappedNative*>(wrapper);
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::GetCurrentJSStack(nsIStackFrame * *aCurrentJSStack)
{
MOZ_ASSERT(aCurrentJSStack, "bad param");
nsCOMPtr<nsIStackFrame> currentStack = dom::GetCurrentJSStack();
currentStack.forget(aCurrentJSStack);
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::GetCurrentNativeCallContext(nsAXPCNativeCallContext * *aCurrentNativeCallContext)
{
MOZ_ASSERT(aCurrentNativeCallContext, "bad param");
*aCurrentNativeCallContext = XPCJSContext::Get()->GetCallContext();
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::SetFunctionThisTranslator(const nsIID & aIID,
nsIXPCFunctionThisTranslator* aTranslator)
{
XPCJSContext* cx = GetContext();
IID2ThisTranslatorMap* map = cx->GetThisTranslatorMap();
map->Add(aIID, aTranslator);
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::CreateSandbox(JSContext* cx, nsIPrincipal* principal,
JSObject** _retval)
{
*_retval = nullptr;
RootedValue rval(cx);
SandboxOptions options;
nsresult rv = CreateSandboxObject(cx, &rval, principal, options);
MOZ_ASSERT(NS_FAILED(rv) || !rval.isPrimitive(),
"Bad return value from xpc_CreateSandboxObject()!");
if (NS_SUCCEEDED(rv) && !rval.isPrimitive()) {
*_retval = rval.toObjectOrNull();
}
return rv;
}
NS_IMETHODIMP
nsXPConnect::EvalInSandboxObject(const nsAString& source, const char* filename,
JSContext* cx, JSObject* sandboxArg,
int32_t jsVersion,
MutableHandleValue rval)
{
#ifdef DEBUG
{
const char *version = JS_VersionToString(JSVersion(jsVersion));
MOZ_ASSERT(version && strcmp(version, "unknown") != 0, "Illegal JS version passed");
}
#endif
if (!sandboxArg)
return NS_ERROR_INVALID_ARG;
RootedObject sandbox(cx, sandboxArg);
nsCString filenameStr;
if (filename) {
filenameStr.Assign(filename);
} else {
filenameStr = NS_LITERAL_CSTRING("x-bogus://XPConnect/Sandbox");
}
return EvalInSandbox(cx, sandbox, source, filenameStr, 1,
JSVersion(jsVersion), rval);
}
NS_IMETHODIMP
nsXPConnect::GetWrappedNativePrototype(JSContext* aJSContext,
JSObject* aScopeArg,
nsIClassInfo* aClassInfo,
JSObject** aRetVal)
{
RootedObject aScope(aJSContext, aScopeArg);
JSAutoCompartment ac(aJSContext, aScope);
XPCWrappedNativeScope* scope = ObjectScope(aScope);
if (!scope)
return UnexpectedFailure(NS_ERROR_FAILURE);
XPCNativeScriptableCreateInfo sciProto;
XPCWrappedNative::GatherProtoScriptableCreateInfo(aClassInfo, sciProto);
AutoMarkingWrappedNativeProtoPtr proto(aJSContext);
proto = XPCWrappedNativeProto::GetNewOrUsed(scope, aClassInfo, &sciProto);
if (!proto)
return UnexpectedFailure(NS_ERROR_FAILURE);
JSObject* protoObj = proto->GetJSProtoObject();
if (!protoObj)
return UnexpectedFailure(NS_ERROR_FAILURE);
*aRetVal = protoObj;
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::DebugDump(int16_t depth)
{
#ifdef DEBUG
depth-- ;
XPC_LOG_ALWAYS(("nsXPConnect @ %x with mRefCnt = %d", this, mRefCnt.get()));
XPC_LOG_INDENT();
XPC_LOG_ALWAYS(("gSelf @ %x", gSelf));
XPC_LOG_ALWAYS(("gOnceAliveNowDead is %d", (int)gOnceAliveNowDead));
if (mContext) {
if (depth)
mContext->DebugDump(depth);
else
XPC_LOG_ALWAYS(("XPCJSContext @ %x", mContext));
} else
XPC_LOG_ALWAYS(("mContext is null"));
XPCWrappedNativeScope::DebugDumpAllScopes(depth);
XPC_LOG_OUTDENT();
#endif
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::DebugDumpObject(nsISupports* p, int16_t depth)
{
#ifdef DEBUG
if (!depth)
return NS_OK;
if (!p) {
XPC_LOG_ALWAYS(("*** Cound not dump object with NULL address"));
return NS_OK;
}
nsCOMPtr<nsIXPConnect> xpc;
nsCOMPtr<nsIXPCWrappedJSClass> wjsc;
nsCOMPtr<nsIXPConnectWrappedNative> wn;
nsCOMPtr<nsIXPConnectWrappedJS> wjs;
if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnect),
getter_AddRefs(xpc)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPConnect..."));
xpc->DebugDump(depth);
} else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPCWrappedJSClass),
getter_AddRefs(wjsc)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPCWrappedJSClass..."));
wjsc->DebugDump(depth);
} else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedNative),
getter_AddRefs(wn)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedNative..."));
wn->DebugDump(depth);
} else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedJS),
getter_AddRefs(wjs)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedJS..."));
wjs->DebugDump(depth);
} else {
XPC_LOG_ALWAYS(("*** Could not dump the nsISupports @ %x", p));
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::DebugDumpJSStack(bool showArgs,
bool showLocals,
bool showThisProps)
{
xpc_DumpJSStack(showArgs, showLocals, showThisProps);
return NS_OK;
}
char*
nsXPConnect::DebugPrintJSStack(bool showArgs,
bool showLocals,
bool showThisProps)
{
JSContext* cx = nsContentUtils::GetCurrentJSContext();
if (!cx)
printf("there is no JSContext on the nsIThreadJSContextStack!\n");
else
return xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps);
return nullptr;
}
NS_IMETHODIMP
nsXPConnect::VariantToJS(JSContext* ctx, JSObject* scopeArg, nsIVariant* value,
MutableHandleValue _retval)
{
NS_PRECONDITION(ctx, "bad param");
NS_PRECONDITION(scopeArg, "bad param");
NS_PRECONDITION(value, "bad param");
RootedObject scope(ctx, scopeArg);
MOZ_ASSERT(js::IsObjectInContextCompartment(scope, ctx));
nsresult rv = NS_OK;
if (!XPCVariant::VariantDataToJS(value, &rv, _retval)) {
if (NS_FAILED(rv))
return rv;
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval)
{
NS_PRECONDITION(ctx, "bad param");
NS_PRECONDITION(_retval, "bad param");
RefPtr<XPCVariant> variant = XPCVariant::newVariant(ctx, value);
variant.forget(_retval);
if (!(*_retval))
return NS_ERROR_FAILURE;
return NS_OK;
}
nsIPrincipal*
nsXPConnect::GetPrincipal(JSObject* obj, bool allowShortCircuit) const
{
MOZ_ASSERT(IS_WN_REFLECTOR(obj), "What kind of wrapper is this?");
XPCWrappedNative* xpcWrapper = XPCWrappedNative::Get(obj);
if (xpcWrapper) {
if (allowShortCircuit) {
nsIPrincipal* result = xpcWrapper->GetObjectPrincipal();
if (result) {
return result;
}
}
// If not, check if it points to an nsIScriptObjectPrincipal
nsCOMPtr<nsIScriptObjectPrincipal> objPrin =
do_QueryInterface(xpcWrapper->Native());
if (objPrin) {
nsIPrincipal* result = objPrin->GetPrincipal();
if (result) {
return result;
}
}
}
return nullptr;
}
namespace xpc {
bool
Base64Encode(JSContext* cx, HandleValue val, MutableHandleValue out)
{
MOZ_ASSERT(cx);
nsAutoCString encodedString;
if (!ConvertJSValueToByteString(cx, val, false, encodedString)) {
return false;
}
nsAutoCString result;
if (NS_FAILED(mozilla::Base64Encode(encodedString, result))) {
JS_ReportErrorASCII(cx, "Failed to encode base64 data!");
return false;
}
JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length());
if (!str)
return false;
out.setString(str);
return true;
}
bool
Base64Decode(JSContext* cx, HandleValue val, MutableHandleValue out)
{
MOZ_ASSERT(cx);
nsAutoCString encodedString;
if (!ConvertJSValueToByteString(cx, val, false, encodedString)) {
return false;
}
nsAutoCString result;
if (NS_FAILED(mozilla::Base64Decode(encodedString, result))) {
JS_ReportErrorASCII(cx, "Failed to decode base64 string!");
return false;
}
JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length());
if (!str)
return false;
out.setString(str);
return true;
}
void
SetLocationForGlobal(JSObject* global, const nsACString& location)
{
MOZ_ASSERT(global);
CompartmentPrivate::Get(global)->SetLocation(location);
}
void
SetLocationForGlobal(JSObject* global, nsIURI* locationURI)
{
MOZ_ASSERT(global);
CompartmentPrivate::Get(global)->SetLocationURI(locationURI);
}
} // namespace xpc
NS_IMETHODIMP
nsXPConnect::NotifyDidPaint()
{
JS::NotifyDidPaint(GetContext()->Context());
return NS_OK;
}
static nsresult
WriteScriptOrFunction(nsIObjectOutputStream* stream, JSContext* cx,
JSScript* scriptArg, HandleObject functionObj)
{
// Exactly one of script or functionObj must be given
MOZ_ASSERT(!scriptArg != !functionObj);
RootedScript script(cx, scriptArg);
if (!script) {
RootedFunction fun(cx, JS_GetObjectFunction(functionObj));
script.set(JS_GetFunctionScript(cx, fun));
}
uint8_t flags = 0; // We don't have flags anymore.
nsresult rv = stream->Write8(flags);
if (NS_FAILED(rv))
return rv;
TranscodeBuffer buffer;
TranscodeResult code;
{
if (functionObj)
code = EncodeInterpretedFunction(cx, buffer, functionObj);
else
code = EncodeScript(cx, buffer, script);
}
if (code != TranscodeResult_Ok) {
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
}
size_t size = buffer.length();
if (size > UINT32_MAX)
return NS_ERROR_FAILURE;
rv = stream->Write32(size);
if (NS_SUCCEEDED(rv))
rv = stream->WriteBytes(reinterpret_cast<char*>(buffer.begin()), size);
return rv;
}
static nsresult
ReadScriptOrFunction(nsIObjectInputStream* stream, JSContext* cx,
JSScript** scriptp, JSObject** functionObjp)
{
// Exactly one of script or functionObj must be given
MOZ_ASSERT(!scriptp != !functionObjp);
uint8_t flags;
nsresult rv = stream->Read8(&flags);
if (NS_FAILED(rv))
return rv;
// We don't serialize mutedError-ness of scripts, which is fine as long as
// we only serialize system and XUL-y things. We can detect this by checking
// where the caller wants us to deserialize.
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome() ||
CurrentGlobalOrNull(cx) == xpc::CompilationScope());
uint32_t size;
rv = stream->Read32(&size);
if (NS_FAILED(rv))
return rv;
char* data;
rv = stream->ReadBytes(size, &data);
if (NS_FAILED(rv))
return rv;
TranscodeBuffer buffer;
buffer.replaceRawBuffer(reinterpret_cast<uint8_t*>(data), size);
{
TranscodeResult code;
if (scriptp) {
Rooted<JSScript*> script(cx);
code = DecodeScript(cx, buffer, &script);
if (code == TranscodeResult_Ok)
*scriptp = script.get();
} else {
Rooted<JSFunction*> funobj(cx);
code = DecodeInterpretedFunction(cx, buffer, &funobj);
if (code == TranscodeResult_Ok)
*functionObjp = JS_GetFunctionObject(funobj.get());
}
if (code != TranscodeResult_Ok) {
if ((code & TranscodeResult_Failure) != 0)
return NS_ERROR_FAILURE;
MOZ_ASSERT((code & TranscodeResult_Throw) != 0);
JS_ClearPendingException(cx);
return NS_ERROR_OUT_OF_MEMORY;
}
}
return rv;
}
NS_IMETHODIMP
nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx, JSScript* script)
{
return WriteScriptOrFunction(stream, cx, script, nullptr);
}
NS_IMETHODIMP
nsXPConnect::ReadScript(nsIObjectInputStream* stream, JSContext* cx, JSScript** scriptp)
{
return ReadScriptOrFunction(stream, cx, scriptp, nullptr);
}
NS_IMETHODIMP
nsXPConnect::WriteFunction(nsIObjectOutputStream* stream, JSContext* cx, JSObject* functionObjArg)
{
RootedObject functionObj(cx, functionObjArg);
return WriteScriptOrFunction(stream, cx, nullptr, functionObj);
}
NS_IMETHODIMP
nsXPConnect::ReadFunction(nsIObjectInputStream* stream, JSContext* cx, JSObject** functionObjp)
{
return ReadScriptOrFunction(stream, cx, nullptr, functionObjp);
}
/* These are here to be callable from a debugger */
extern "C" {
JS_EXPORT_API(void) DumpJSStack()
{
xpc_DumpJSStack(true, true, false);
}
JS_EXPORT_API(char*) PrintJSStack()
{
nsresult rv;
nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
return (NS_SUCCEEDED(rv) && xpc) ?
xpc->DebugPrintJSStack(true, true, false) :
nullptr;
}
JS_EXPORT_API(void) DumpCompleteHeap()
{
nsCOMPtr<nsICycleCollectorListener> listener =
do_CreateInstance("@mozilla.org/cycle-collector-logger;1");
if (!listener) {
NS_WARNING("Failed to create CC logger");
return;
}
nsCOMPtr<nsICycleCollectorListener> alltracesListener;
listener->AllTraces(getter_AddRefs(alltracesListener));
if (!alltracesListener) {
NS_WARNING("Failed to get all traces logger");
return;
}
nsJSContext::CycleCollectNow(alltracesListener);
}
} // extern "C"
namespace xpc {
bool
Atob(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.length())
return true;
return xpc::Base64Decode(cx, args[0], args.rval());
}
bool
Btoa(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.length())
return true;
return xpc::Base64Encode(cx, args[0], args.rval());
}
bool
IsXrayWrapper(JSObject* obj)
{
return WrapperFactory::IsXrayWrapper(obj);
}
JSAddonId*
NewAddonId(JSContext* cx, const nsACString& id)
{
JS::RootedString str(cx, JS_NewStringCopyN(cx, id.BeginReading(), id.Length()));
if (!str)
return nullptr;
return JS::NewAddonId(cx, str);
}
bool
SetAddonInterposition(const nsACString& addonIdStr, nsIAddonInterposition* interposition)
{
JSAddonId* addonId;
// We enter the junk scope just to allocate a string, which actually will go
// in the system zone.
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::PrivilegedJunkScope()))
return false;
addonId = NewAddonId(jsapi.cx(), addonIdStr);
if (!addonId)
return false;
return XPCWrappedNativeScope::SetAddonInterposition(jsapi.cx(), addonId, interposition);
}
bool
AllowCPOWsInAddon(const nsACString& addonIdStr, bool allow)
{
JSAddonId* addonId;
// We enter the junk scope just to allocate a string, which actually will go
// in the system zone.
AutoJSAPI jsapi;
if (!jsapi.Init(xpc::PrivilegedJunkScope()))
return false;
addonId = NewAddonId(jsapi.cx(), addonIdStr);
if (!addonId)
return false;
return XPCWrappedNativeScope::AllowCPOWsInAddon(jsapi.cx(), addonId, allow);
}
} // namespace xpc
namespace mozilla {
namespace dom {
bool
IsChromeOrXBL(JSContext* cx, JSObject* /* unused */)
{
MOZ_ASSERT(NS_IsMainThread());
JSCompartment* c = js::GetContextCompartment(cx);
// For remote XUL, we run XBL in the XUL scope. Given that we care about
// compat and not security for remote XUL, we just always claim to be XBL.
//
// Note that, for performance, we don't check AllowXULXBLForPrincipal here,
// and instead rely on the fact that AllowContentXBLScope() only returns false in
// remote XUL situations.
return AccessCheck::isChrome(c) || IsContentXBLScope(c) || !AllowContentXBLScope(c);
}
namespace workers {
extern bool IsCurrentThreadRunningChromeWorker();
} // namespace workers
bool
ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj)
{
if (NS_IsMainThread()) {
return IsChromeOrXBL(cx, obj);
}
return workers::IsCurrentThreadRunningChromeWorker();
}
} // namespace dom
} // namespace mozilla