Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
/* High level class and public functions implementation. */
#include "js/Transcoding.h"
#include "mozilla/Assertions.h"
#include "mozilla/Base64.h"
#include "mozilla/Likely.h"
#include "mozilla/Unused.h"
#include "XPCWrapper.h"
#include "jsfriendapi.h"
#include "js/AllocationLogging.h" // JS::SetLogCtorDtorFunctions
#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions
#include "js/Object.h" // JS::GetClass
#include "js/ProfilingStack.h"
#include "GeckoProfiler.h"
#include "mozJSModuleLoader.h"
#include "nsJSEnvironment.h"
#include "nsThreadUtils.h"
#include "nsDOMJSUtils.h"
#include "WrapperFactory.h"
#include "AccessCheck.h"
#include "JSServices.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/glean/bindings/Glean.h"
#include "mozilla/glean/bindings/GleanPings.h"
#include "mozilla/ScriptPreloader.h"
#include "nsDOMMutationObserver.h"
#include "nsICycleCollectorListener.h"
#include "nsCycleCollector.h"
#include "nsIOService.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsScriptSecurityManager.h"
#include "nsContentUtils.h"
#include "nsScriptError.h"
#include "nsJSUtils.h"
#include "nsRFPService.h"
#include "prsystem.h"
#include "xpcprivate.h"
#ifdef XP_WIN
# include "mozilla/WinHeaderOnlyUtils.h"
#else
# include <sys/mman.h>
#endif
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_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";
/***************************************************************************/
nsXPConnect::nsXPConnect() {
#ifdef MOZ_GECKO_PROFILER
JS::SetProfilingThreadCallbacks(profiler_register_thread,
profiler_unregister_thread);
#endif
}
// static
void nsXPConnect::InitJSContext() {
MOZ_ASSERT(!gSelf->mContext);
XPCJSContext* xpccx = XPCJSContext::NewXPCJSContext();
if (!xpccx) {
MOZ_CRASH("Couldn't create XPCJSContext.");
}
gSelf->mContext = xpccx;
gSelf->mRuntime = xpccx->Runtime();
mozJSModuleLoader::InitStatics();
// Initialize the script preloader cache.
Unused << mozilla::ScriptPreloader::GetSingleton();
nsJSContext::EnsureStatics();
}
void xpc::InitializeJSContext() { nsXPConnect::InitJSContext(); }
nsXPConnect::~nsXPConnect() {
MOZ_ASSERT(mRuntime);
mRuntime->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. :-(
//
// seems to cause crashes.
mRuntime->GarbageCollect(JS::GCOptions::Normal,
JS::GCReason::XPCONNECT_SHUTDOWN);
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.
mRuntime->GarbageCollect(JS::GCOptions::Normal,
JS::GCReason::XPCONNECT_SHUTDOWN);
NS_RELEASE(gSystemPrincipal);
gScriptSecurityManager = nullptr;
// shutdown the logging system
XPC_LOG_FINISH();
delete mContext;
MOZ_ASSERT(gSelf == this);
gSelf = nullptr;
gOnceAliveNowDead = true;
}
// static
void nsXPConnect::InitStatics() {
#ifdef NS_BUILD_REFCNT_LOGGING
// These functions are used for reporting leaks, so we register them as early
// as possible to avoid missing any classes' creations.
JS::SetLogCtorDtorFunctions(NS_LogCtor, NS_LogDtor);
#endif
gSelf = new nsXPConnect();
gOnceAliveNowDead = false;
// 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);
}
// static
void nsXPConnect::ReleaseXPConnectSingleton() {
nsXPConnect* xpc = gSelf;
if (xpc) {
nsrefcnt cnt;
NS_RELEASE2(xpc, cnt);
}
mozJSModuleLoader::ShutdownLoaders();
}
// static
XPCJSRuntime* nsXPConnect::GetRuntimeInstance() {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
return gSelf->mRuntime;
}
void xpc::ErrorBase::Init(JSErrorBase* aReport) {
if (!aReport->filename) {
mFileName.SetIsVoid(true);
} else {
mFileName.Assign(aReport->filename.c_str());
}
mSourceId = aReport->sourceId;
mLineNumber = aReport->lineno;
mColumn = aReport->column.oneOriginValue();
}
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 ? "chrome javascript"_ns : "content javascript"_ns;
mWindowID = aWindowID;
if (aToStringResult) {
AppendUTF8toUTF16(mozilla::MakeStringSpan(aToStringResult), mErrorMsg);
}
if (mErrorMsg.IsEmpty()) {
ErrorReportToMessageString(aReport, mErrorMsg);
}
if (mErrorMsg.IsEmpty()) {
mErrorMsg.AssignLiteral("<unknown>");
}
if (aReport->errorMessageName) {
mErrorMsgName.AssignASCII(aReport->errorMessageName);
} else {
mErrorMsgName.Truncate();
}
mIsWarning = aReport->isWarning();
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 ? "chrome javascript"_ns : "content javascript"_ns;
mWindowID = aWindowID;
aException->GetErrorMessage(mErrorMsg);
aException->GetFilename(aCx, mFileName);
if (mFileName.IsEmpty()) {
mFileName.SetIsVoid(true);
}
mSourceId = aException->SourceId(aCx);
mLineNumber = aException->LineNumber(aCx);
mColumn = aException->ColumnNumber();
}
static LazyLogModule gJSDiagnostics("JSDiagnostics");
void xpc::ErrorBase::AppendErrorDetailsTo(nsCString& error) {
error.Append(mFileName);
error.AppendLiteral(", line ");
error.AppendInt(mLineNumber, 10);
error.AppendLiteral(": ");
AppendUTF16toUTF8(mErrorMsg, error);
}
void xpc::ErrorNote::LogToStderr() {
if (!nsJSUtils::DumpEnabled()) {
return;
}
nsAutoCString error;
error.AssignLiteral("JavaScript note: ");
AppendErrorDetailsTo(error);
fprintf(stderr, "%s\n", error.get());
fflush(stderr);
}
void xpc::ErrorReport::LogToStderr() {
if (!nsJSUtils::DumpEnabled()) {
return;
}
nsAutoCString error;
error.AssignLiteral("JavaScript ");
if (IsWarning()) {
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, JS::NothingHandleValue, nullptr, nullptr);
}
void xpc::ErrorReport::LogToConsoleWithStack(
nsGlobalWindowInner* aWin, JS::Handle<mozilla::Maybe<JS::Value>> aException,
JS::HandleObject aStack, JS::HandleObject aStackGlobal) {
if (aStack) {
MOZ_ASSERT(aStackGlobal);
MOZ_ASSERT(JS_IsGlobalObject(aStackGlobal));
js::AssertSameCompartment(aStack, aStackGlobal);
} else {
MOZ_ASSERT(!aStackGlobal);
}
LogToStderr();
MOZ_LOG(gJSDiagnostics, IsWarning() ? LogLevel::Warning : LogLevel::Error,
("file %s, line %u\n%s", mFileName.get(), mLineNumber,
NS_ConvertUTF16toUTF8(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 =
CreateScriptError(aWin, aException, aStack, aStackGlobal);
errorObject->SetErrorMessageName(mErrorMsgName);
uint32_t flags =
mIsWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag;
nsresult rv = errorObject->InitWithWindowID(
mErrorMsg, mFileName, mLineNumber, mColumn, flags, mCategory, mWindowID,
mCategory.Equals("chrome javascript"_ns));
NS_ENSURE_SUCCESS_VOID(rv);
rv = errorObject->InitSourceId(mSourceId);
NS_ENSURE_SUCCESS_VOID(rv);
rv = errorObject->InitIsPromiseRejection(mIsPromiseRejection);
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.mSourceId,
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()) {
// Don't prefix warnings with an often misleading name like "Error: ".
if (!aReport->isWarning()) {
JSLinearString* name = js::GetErrorTypeName(
CycleCollectedJSContext::Get()->Context(), aReport->exnType);
if (name) {
AssignJSLinearString(aString, name);
aString.AppendLiteral(": ");
}
}
aString.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str()));
}
}
/***************************************************************************/
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::GetClass(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.
if (xpc::CompartmentPrivate* priv = xpc::CompartmentPrivate::Get(obj)) {
MOZ_ASSERT(priv->GetScope());
priv->GetScope()->TraceInside(trc);
}
}
namespace xpc {
JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp,
nsIPrincipal* principal,
JS::RealmOptions& 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);
{
SiteIdentifier site;
nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
NS_ENSURE_SUCCESS(rv, nullptr);
global = JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal),
JS::DontFireOnNewGlobalHook, aOptions);
if (!global) {
return nullptr;
}
JSAutoRealm ar(cx, global);
RealmPrivate::Init(global, site);
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 JSClass*)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::RealmOptions& aOptions,
bool aIsSystemPrincipal, bool aSecureContext,
bool aForceUTC, bool aAlwaysUseFdlibm,
bool aLocaleEnUS) {
if (aIsSystemPrincipal) {
// Make toSource functions [ChromeOnly]
aOptions.creationOptions().setToSourceEnabled(true);
// Make sure [SecureContext] APIs are visible:
aOptions.creationOptions().setSecureContext(true);
aOptions.behaviors().setClampAndJitterTime(false);
aOptions.behaviors().setDiscardSource(ShouldDiscardSystemSource());
MOZ_ASSERT(aSecureContext,
"aIsSystemPrincipal should imply aSecureContext");
} else {
aOptions.creationOptions().setSecureContext(aSecureContext);
}
aOptions.creationOptions().setForceUTC(aForceUTC);
aOptions.creationOptions().setAlwaysUseFdlibm(aAlwaysUseFdlibm);
if (aLocaleEnUS) {
nsCString locale = nsRFPService::GetSpoofedJSLocale();
aOptions.creationOptions().setLocaleCopyZ(locale.get());
}
}
bool InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal,
uint32_t aFlags) {
// Immediately enter the global's realm so that everything we create
// ends up there.
JSAutoRealm ar(aJSContext, aGlobal);
// Stuff coming through this path always ends up as a DOM global.
MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL);
if (!(aFlags & xpc::OMIT_COMPONENTS_OBJECT)) {
// XPCCallContext gives us an active request needed to save/restore.
if (!ObjectScope(aGlobal)->AttachComponentsObject(aJSContext) ||
!XPCNativeWrapper::AttachNewConstructorObject(aJSContext, aGlobal)) {
return UnexpectedFailure(false);
}
if (!mozJSModuleLoader::Get()->DefineJSServices(aJSContext, aGlobal)) {
return UnexpectedFailure(false);
}
}
if (!(aFlags & xpc::DONT_FIRE_ONNEWGLOBALHOOK)) {
JS_FireOnNewGlobalObject(aJSContext, aGlobal);
}
return true;
}
nsresult InitClassesWithNewWrappedGlobal(JSContext* aJSContext,
nsISupports* aCOMObj,
nsIPrincipal* aPrincipal,
uint32_t aFlags,
JS::RealmOptions& aOptions,
MutableHandleObject aNewGlobal) {
MOZ_ASSERT(aJSContext, "bad param");
MOZ_ASSERT(aCOMObj, "bad param");
// We pass null for the 'extra' pointer during global object creation, so
// we need to have a principal.
MOZ_ASSERT(aPrincipal);
// All uses (at time of writing) were System Principal, meaning
// aShouldResistFingerprinting can be hardcoded to false.
// If this changes, ShouldRFP needs to be updated accordingly.
MOZ_RELEASE_ASSERT(aPrincipal->IsSystemPrincipal());
// Similarly we can thus hardcode the RTPCallerType.
aOptions.behaviors().setReduceTimerPrecisionCallerType(
RTPCallerTypeToToken(RTPCallerType::SystemPrincipal));
InitGlobalObjectOptions(aOptions, /* aSystemPrincipal */ true,
/* aSecureContext */ true,
/* aForceUTC */ false, /* aAlwaysUseFdlibm */ false,
/* aLocaleEnUS */ false);
// Call into XPCWrappedNative to make a new global object, scope, and global
// prototype.
xpcObjectHelper helper(aCOMObj);
MOZ_ASSERT(helper.GetScriptableFlags() & XPC_SCRIPTABLE_IS_GLOBAL_OBJECT);
RefPtr<XPCWrappedNative> wrappedGlobal;
nsresult rv = XPCWrappedNative::WrapNewGlobal(
aJSContext, helper, aPrincipal, 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);
}
{ // Scope for JSAutoRealm
JSAutoRealm ar(aJSContext, global);
if (!JS_DefineProfilingFunctions(aJSContext, global)) {
return UnexpectedFailure(NS_ERROR_OUT_OF_MEMORY);
}
if (aPrincipal->IsSystemPrincipal()) {
if (!glean::Glean::DefineGlean(aJSContext, global) ||
!glean::GleanPings::DefineGleanPings(aJSContext, global)) {
return UnexpectedFailure(NS_ERROR_FAILURE);
}
}
}
aNewGlobal.set(global);
return NS_OK;
}
nsCString GetFunctionName(JSContext* cx, HandleObject obj) {
RootedObject inner(cx, js::UncheckedUnwrap(obj));
JSAutoRealm ar(cx, inner);
RootedFunction fun(cx, JS_GetObjectFunction(inner));
if (!fun) {
// If the object isn't a function, it's likely that it has a single
// function property (for things like nsITimerCallback). In this case,
// return the name of that function property.
Rooted<IdVector> idArray(cx, IdVector(cx));
if (!JS_Enumerate(cx, inner, &idArray)) {
JS_ClearPendingException(cx);
return nsCString("error");
}
if (idArray.length() != 1) {
return nsCString("nonfunction");
}
RootedId id(cx, idArray[0]);
RootedValue v(cx);
if (!JS_GetPropertyById(cx, inner, id, &v)) {
JS_ClearPendingException(cx);
return nsCString("nonfunction");
}
if (!v.isObject()) {
return nsCString("nonfunction");
}
RootedObject vobj(cx, &v.toObject());
return GetFunctionName(cx, vobj);
}
RootedString funName(cx, JS_GetMaybePartialFunctionDisplayId(fun));
RootedScript script(cx, JS_GetFunctionScript(cx, fun));
const char* filename = script ? JS_GetScriptFilename(script) : "anonymous";
const char* filenameSuffix = strrchr(filename, '/');
if (filenameSuffix) {
filenameSuffix++;
} else {
filenameSuffix = filename;
}
nsCString displayName("anonymous");
if (funName) {
RootedValue funNameVal(cx, StringValue(funName));
if (!XPCConvert::JSData2Native(cx, &displayName, funNameVal,
{nsXPTType::T_UTF8STRING}, nullptr, 0,
nullptr)) {
JS_ClearPendingException(cx);
return nsCString("anonymous");
}
}
displayName.Append('[');
displayName.Append(filenameSuffix, strlen(filenameSuffix));
displayName.Append(']');
return displayName;
}
} // namespace xpc
static nsresult NativeInterface2JSObject(JSContext* aCx, HandleObject aScope,
nsISupports* aCOMObj,
nsWrapperCache* aCache,
const nsIID* aIID, bool aAllowWrapping,
MutableHandleValue aVal) {
JSAutoRealm ar(aCx, aScope);
nsresult rv;
xpcObjectHelper helper(aCOMObj, aCache);
if (!XPCConvert::NativeInterface2JSObject(aCx, aVal, 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;
}
nsresult nsIXPConnect::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(aJSContext, aScope, aCOMObj, nullptr,
&aIID, true, &v);
if (NS_FAILED(rv)) {
return rv;
}
if (!v.isObjectOrNull()) {
return NS_ERROR_FAILURE;
}
*aRetVal = v.toObjectOrNull();
return NS_OK;
}
nsresult nsIXPConnect::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(aJSContext, aScope, aCOMObj, aCache, aIID,
aAllowWrapping, aVal);
}
nsresult nsIXPConnect::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);
nsresult rv = NS_ERROR_UNEXPECTED;
if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj, &aIID,
nullptr, &rv))
return rv;
return NS_OK;
}
nsresult nsIXPConnect::JSValToVariant(JSContext* cx, HandleValue aJSVal,
nsIVariant** aResult) {
MOZ_ASSERT(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;
}
nsresult nsIXPConnect::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(aJSContext, result, aJSObj, &aIID,
aOuter, &rv))
return rv;
return NS_OK;
}
nsresult nsIXPConnect::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::CheckedUnwrapDynamic(aJSObj, aJSContext,
/* stopAtWindowProxy = */ false);
if (!aJSObj || !IsWrappedNativeReflector(aJSObj)) {
*_retval = nullptr;
return NS_ERROR_FAILURE;
}
RefPtr<XPCWrappedNative> temp = XPCWrappedNative::Get(aJSObj);
temp.forget(_retval);
return NS_OK;
}
static already_AddRefed<nsISupports> ReflectorToISupports(JSObject* reflector) {
if (!reflector) {
return nullptr;
}
// Try XPCWrappedNatives.
if (IsWrappedNativeReflector(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();
}
already_AddRefed<nsISupports> xpc::ReflectorToISupportsStatic(
JSObject* reflector) {
// Unwrap security wrappers, if allowed.
return ReflectorToISupports(js::CheckedUnwrapStatic(reflector));
}
already_AddRefed<nsISupports> xpc::ReflectorToISupportsDynamic(
JSObject* reflector, JSContext* cx) {
// Unwrap security wrappers, if allowed.
return ReflectorToISupports(
js::CheckedUnwrapDynamic(reflector, cx,
/* stopAtWindowProxy = */ false));
}
nsresult nsIXPConnect::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;
}
nsresult nsIXPConnect::EvalInSandboxObject(const nsAString& source,
const char* filename, JSContext* cx,
JSObject* sandboxArg,
MutableHandleValue rval) {
if (!sandboxArg) {
return NS_ERROR_INVALID_ARG;
}
RootedObject sandbox(cx, sandboxArg);
nsCString filenameStr;
if (filename) {
filenameStr.Assign(filename);
} else {
}
return EvalInSandbox(cx, sandbox, source, filenameStr, 1,
/* enforceFilenameRestrictions */ true, rval);
}
nsresult nsIXPConnect::DebugDump(int16_t depth) {
#ifdef DEBUG
auto* self = static_cast<nsXPConnect*>(this);
depth--;
XPC_LOG_ALWAYS(
("nsXPConnect @ %p with mRefCnt = %" PRIuPTR, self, self->mRefCnt.get()));
XPC_LOG_INDENT();
XPC_LOG_ALWAYS(("gSelf @ %p", self->gSelf));
XPC_LOG_ALWAYS(("gOnceAliveNowDead is %d", (int)self->gOnceAliveNowDead));
XPCWrappedNativeScope::DebugDumpAllScopes(depth);
XPC_LOG_OUTDENT();
#endif
return NS_OK;
}
nsresult nsIXPConnect::DebugDumpObject(nsISupports* aCOMObj, int16_t depth) {
#ifdef DEBUG
if (!depth) {
return NS_OK;
}
if (!aCOMObj) {
XPC_LOG_ALWAYS(("*** Cound not dump object with NULL address"));
return NS_OK;
}
nsCOMPtr<nsIXPConnect> xpc;
nsCOMPtr<nsIXPConnectWrappedNative> wn;
nsCOMPtr<nsIXPConnectWrappedJS> wjs;
if (NS_SUCCEEDED(aCOMObj->QueryInterface(NS_GET_IID(nsIXPConnect),
getter_AddRefs(xpc)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPConnect..."));
xpc->DebugDump(depth);
} else if (NS_SUCCEEDED(aCOMObj->QueryInterface(
NS_GET_IID(nsIXPConnectWrappedNative), getter_AddRefs(wn)))) {
XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedNative..."));
wn->DebugDump(depth);
} else if (NS_SUCCEEDED(aCOMObj->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 @ %p", aCOMObj));
}
#endif
return NS_OK;
}
nsresult nsIXPConnect::DebugDumpJSStack(bool showArgs, bool showLocals,
bool showThisProps) {
xpc_DumpJSStack(showArgs, showLocals, showThisProps);
return NS_OK;
}
nsresult nsIXPConnect::VariantToJS(JSContext* ctx, JSObject* scopeArg,
nsIVariant* value,
MutableHandleValue _retval) {
MOZ_ASSERT(ctx, "bad param");
MOZ_ASSERT(scopeArg, "bad param");
MOZ_ASSERT(value, "bad param");
RootedObject scope(ctx, scopeArg);
MOZ_ASSERT(js::IsObjectInContextCompartment(scope, ctx));
nsresult rv = NS_OK;
if (!XPCVariant::VariantDataToJS(ctx, value, &rv, _retval)) {
if (NS_FAILED(rv)) {
return rv;
}
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult nsIXPConnect::JSToVariant(JSContext* ctx, HandleValue value,
nsIVariant** _retval) {
MOZ_ASSERT(ctx, "bad param");
MOZ_ASSERT(_retval, "bad param");
RefPtr<XPCVariant> variant = XPCVariant::newVariant(ctx, value);
variant.forget(_retval);
if (!(*_retval)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
namespace xpc {
bool Base64Encode(JSContext* cx, HandleValue val, MutableHandleValue out) {
MOZ_ASSERT(cx);
nsAutoCString encodedString;
BindingCallContext callCx(cx, "Base64Encode");
if (!ConvertJSValueToByteString(callCx, val, false, "value", 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;
BindingCallContext callCx(cx, "Base64Decode");
if (!ConvertJSValueToByteString(callCx, val, false, "value", 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);
RealmPrivate::Get(global)->SetLocation(location);
}
void SetLocationForGlobal(JSObject* global, nsIURI* locationURI) {
MOZ_ASSERT(global);
RealmPrivate::Get(global)->SetLocationURI(locationURI);
}
} // namespace xpc
// static
nsIXPConnect* nsIXPConnect::XPConnect() {
// Do a release-mode assert that we're not doing anything significant in
// XPConnect off the main thread. If you're an extension developer hitting
if (!MOZ_LIKELY(NS_IsMainThread())) {
MOZ_CRASH();
}
return nsXPConnect::gSelf;
}
/* These are here to be callable from a debugger */
extern "C" {
MOZ_EXPORT void DumpJSStack() { xpc_DumpJSStack(true, true, false); }
MOZ_EXPORT void DumpCompleteHeap() {
nsCOMPtr<nsICycleCollectorListener> listener =
nsCycleCollector_createLogger();
MOZ_ASSERT(listener);
nsCOMPtr<nsICycleCollectorListener> alltracesListener;
listener->AllTraces(getter_AddRefs(alltracesListener));
if (!alltracesListener) {
NS_WARNING("Failed to get all traces logger");
return;
}
nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, 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); }
} // namespace xpc
namespace mozilla {
namespace dom {
bool IsChromeOrUAWidget(JSContext* cx, JSObject* /* unused */) {
MOZ_ASSERT(NS_IsMainThread());
JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
MOZ_ASSERT(realm);
JS::Compartment* c = JS::GetCompartmentForRealm(realm);
return AccessCheck::isChrome(c) || IsUAWidgetCompartment(c);
}
bool IsNotUAWidget(JSContext* cx, JSObject* /* unused */) {
MOZ_ASSERT(NS_IsMainThread());
JS::Realm* realm = JS::GetCurrentRealmOrNull(cx);
MOZ_ASSERT(realm);
JS::Compartment* c = JS::GetCompartmentForRealm(realm);
return !IsUAWidgetCompartment(c);
}
extern bool IsCurrentThreadRunningChromeWorker();
bool ThreadSafeIsChromeOrUAWidget(JSContext* cx, JSObject* obj) {
if (NS_IsMainThread()) {
return IsChromeOrUAWidget(cx, obj);
}
return IsCurrentThreadRunningChromeWorker();
}
} // namespace dom
} // namespace mozilla
#ifdef MOZ_TSAN
ReadOnlyPage ReadOnlyPage::sInstance;
#else
constexpr const volatile ReadOnlyPage ReadOnlyPage::sInstance;
#endif
void xpc::ReadOnlyPage::Write(const volatile bool* aPtr, bool aValue) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (*aPtr == aValue) return;
// Please modify the definition of kAutomationPageSize if a new platform
// is running in automation and hits this assertion.
MOZ_RELEASE_ASSERT(PR_GetPageSize() == alignof(ReadOnlyPage));
MOZ_RELEASE_ASSERT(
reinterpret_cast<uintptr_t>(&sInstance) % alignof(ReadOnlyPage) == 0);
#ifdef XP_WIN
AutoVirtualProtect prot(const_cast<ReadOnlyPage*>(&sInstance),
alignof(ReadOnlyPage), PAGE_READWRITE);
MOZ_RELEASE_ASSERT(prot && (prot.PrevProt() & 0xFF) == PAGE_READONLY);
#else
int ret = mprotect(const_cast<ReadOnlyPage*>(&sInstance),
alignof(ReadOnlyPage), PROT_READ | PROT_WRITE);
MOZ_RELEASE_ASSERT(ret == 0);
#endif
MOZ_RELEASE_ASSERT(aPtr == &sInstance.mNonLocalConnectionsDisabled ||
aPtr == &sInstance.mTurnOffAllSecurityPref);
#ifdef XP_WIN
BOOL ret = WriteProcessMemory(GetCurrentProcess(), const_cast<bool*>(aPtr),
&aValue, sizeof(bool), nullptr);
MOZ_RELEASE_ASSERT(ret);
#else
*const_cast<volatile bool*>(aPtr) = aValue;
ret = mprotect(const_cast<ReadOnlyPage*>(&sInstance), alignof(ReadOnlyPage),
PROT_READ);
MOZ_RELEASE_ASSERT(ret == 0);
#endif
}
void xpc::ReadOnlyPage::Init() {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
static_assert(alignof(ReadOnlyPage) == kAutomationPageSize);
static_assert(sizeof(sInstance) == alignof(ReadOnlyPage));
// Make sure that initialization is not too late.
MOZ_DIAGNOSTIC_ASSERT(!net::gIOService);
char* s = getenv("MOZ_DISABLE_NONLOCAL_CONNECTIONS");
const bool disabled = s && *s != '0';
Write(&sInstance.mNonLocalConnectionsDisabled, disabled);
if (!disabled) {
// not bothered to check automation prefs.
return;
}
// The obvious thing is to make this pref a static pref. But then it would
// always be defined and always show up in about:config, and users could flip
// it, which we don't want. Instead we roll our own callback so that if the
// pref is undefined (the normal case) then sAutomationPrefIsSet is false and
// nothing shows up in about:config.
nsresult rv = Preferences::RegisterCallbackAndCall(
[](const char* aPrefName, void* /* aClosure */) {
Write(&sInstance.mTurnOffAllSecurityPref,
Preferences::GetBool(aPrefName, /* aFallback */ false));
},
"security."
"turn_off_all_security_so_that_viruses_can_take_over_this_computer");
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
}