summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src/nsXPConnect.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src/nsXPConnect.cpp')
-rw-r--r--js/xpconnect/src/nsXPConnect.cpp1181
1 files changed, 1181 insertions, 0 deletions
diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp
new file mode 100644
index 0000000000..b8bff8fba6
--- /dev/null
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -0,0 +1,1181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* High level class and public functions implementation. */
+
+#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 "nsJSEnvironment.h"
+#include "nsThreadUtils.h"
+#include "nsDOMJSUtils.h"
+
+#include "WrapperFactory.h"
+#include "AccessCheck.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/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 "prsystem.h"
+
+#ifndef XP_WIN
+# 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() : mShuttingDown(false) {
+#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();
+
+ mozJSComponentLoader::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. :-(
+ mRuntime->GarbageCollect(JS::GCReason::XPCONNECT_SHUTDOWN);
+
+ mShuttingDown = true;
+ XPCWrappedNativeScope::SystemIsBeingShutDown();
+ mRuntime->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::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
+ ReadOnlyPage::Init();
+
+ 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);
+ }
+
+ mozJSComponentLoader::Shutdown();
+}
+
+// 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 {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(aReport->filename), mFileName);
+ }
+
+ mSourceId = aReport->sourceId;
+ 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 ? "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>");
+ }
+
+ mSourceLine.Assign(aReport->linebuf(), aReport->linebufLength());
+
+ 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) {
+ AppendUTF16toUTF8(mFileName, error);
+ 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", NS_ConvertUTF16toUTF8(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, mSourceLine, 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,
+ nsIPrincipal* aPrincipal) {
+ bool shouldDiscardSystemSource = ShouldDiscardSystemSource();
+
+ bool isSystem = aPrincipal->IsSystemPrincipal();
+
+ if (isSystem) {
+ // Make toSource functions [ChromeOnly]
+ aOptions.creationOptions().setToSourceEnabled(true);
+ // Make sure [SecureContext] APIs are visible:
+ aOptions.creationOptions().setSecureContext(true);
+ aOptions.behaviors().setClampAndJitterTime(false);
+ }
+
+ if (shouldDiscardSystemSource) {
+ bool discardSource = isSystem;
+
+ aOptions.behaviors().setDiscardSource(discardSource);
+ }
+}
+
+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 (!(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);
+
+ InitGlobalObjectOptions(aOptions, aPrincipal);
+
+ // 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, aFlags & xpc::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);
+ }
+
+ { // Scope for JSAutoRealm
+ JSAutoRealm ar(aJSContext, global);
+ if (!JS_DefineProfilingFunctions(aJSContext, global)) {
+ return UnexpectedFailure(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ aNewGlobal.set(global);
+ return NS_OK;
+}
+
+} // 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;
+}
+
+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(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;
+}
+
+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(aJSContext, aScope, aCOMObj, aCache, aIID,
+ aAllowWrapping, aVal);
+}
+
+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);
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj, &aIID,
+ nullptr, &rv))
+ return rv;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPConnect::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;
+}
+
+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(aJSContext, 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::CheckedUnwrapDynamic(aJSObj, aJSContext,
+ /* 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;
+}
+
+static already_AddRefed<nsISupports> ReflectorToISupports(JSObject* reflector) {
+ 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();
+}
+
+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));
+}
+
+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,
+ MutableHandleValue rval) {
+ if (!sandboxArg) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RootedObject sandbox(cx, sandboxArg);
+ nsCString filenameStr;
+ if (filename) {
+ filenameStr.Assign(filename);
+ } else {
+ filenameStr = "x-bogus://XPConnect/Sandbox"_ns;
+ }
+ return EvalInSandbox(cx, sandbox, source, filenameStr, 1,
+ /* enforceFilenameRestrictions */ true, rval);
+}
+
+NS_IMETHODIMP
+nsXPConnect::DebugDump(int16_t depth) {
+#ifdef DEBUG
+ depth--;
+ XPC_LOG_ALWAYS(
+ ("nsXPConnect @ %p with mRefCnt = %" PRIuPTR, this, mRefCnt.get()));
+ XPC_LOG_INDENT();
+ XPC_LOG_ALWAYS(("gSelf @ %p", gSelf));
+ XPC_LOG_ALWAYS(("gOnceAliveNowDead is %d", (int)gOnceAliveNowDead));
+ 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<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(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 @ %p", p));
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPConnect::DebugDumpJSStack(bool showArgs, bool showLocals,
+ bool showThisProps) {
+ xpc_DumpJSStack(showArgs, showLocals, showThisProps);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPConnect::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;
+}
+
+NS_IMETHODIMP
+nsXPConnect::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
+
+NS_IMETHODIMP
+nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx,
+ JSScript* scriptArg) {
+ RootedScript script(cx, scriptArg);
+
+ 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;
+ 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)) {
+ // Ideally we could just pass "buffer" here. See bug 1566574.
+ rv = stream->WriteBytes(Span(buffer.begin(), size));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXPConnect::ReadScript(nsIObjectInputStream* stream, JSContext* cx,
+ const JS::ReadOnlyCompileOptions& options,
+ JSScript** scriptp) {
+ 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.
+ //
+ // CompilationScope() could theoretically GC, so get that out of the way
+ // before comparing to the cx global.
+ JSObject* loaderGlobal = xpc::CompilationScope();
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(cx) ||
+ CurrentGlobalOrNull(cx) == loaderGlobal);
+
+ 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;
+ Rooted<JSScript*> script(cx);
+ code = DecodeScript(cx, options, buffer, &script);
+ if (code == TranscodeResult_Ok) {
+ *scriptp = script.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::GetIsShuttingDown(bool* aIsShuttingDown) {
+ if (!aIsShuttingDown) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aIsShuttingDown = mShuttingDown;
+
+ return NS_OK;
+}
+
+// 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
+ // this, you need to change your code. See bug 716167.
+ 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(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));
+}