/* -*- 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 consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(consoleService); RefPtr 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 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 wjsug = do_QueryInterface(aWrappedJS); Unused << wjsug; MOZ_ASSERT(!wjsug, "One should never be able to QI to " "nsIXPConnectWrappedJSUnmarkGray successfully!"); } /***************************************************************************/ /***************************************************************************/ // nsIXPConnect interface methods... template 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 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 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 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 temp = XPCWrappedNative::Get(aJSObj); temp.forget(_retval); return NS_OK; } already_AddRefed 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 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 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 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(wrapper); return NS_OK; } NS_IMETHODIMP nsXPConnect::GetCurrentJSStack(nsIStackFrame * *aCurrentJSStack) { MOZ_ASSERT(aCurrentJSStack, "bad param"); nsCOMPtr 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 xpc; nsCOMPtr wjsc; nsCOMPtr wn; nsCOMPtr 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 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 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(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(data), size); { TranscodeResult code; if (scriptp) { Rooted script(cx); code = DecodeScript(cx, buffer, &script); if (code == TranscodeResult_Ok) *scriptp = script.get(); } else { Rooted 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 xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); return (NS_SUCCEEDED(rv) && xpc) ? xpc->DebugPrintJSStack(true, true, false) : nullptr; } JS_EXPORT_API(void) DumpCompleteHeap() { nsCOMPtr listener = do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); if (!listener) { NS_WARNING("Failed to create CC logger"); return; } nsCOMPtr 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