diff options
Diffstat (limited to 'js/xpconnect/src')
55 files changed, 29943 insertions, 0 deletions
diff --git a/js/xpconnect/src/BackstagePass.h b/js/xpconnect/src/BackstagePass.h new file mode 100644 index 0000000000..fd19348e86 --- /dev/null +++ b/js/xpconnect/src/BackstagePass.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef BackstagePass_h__ +#define BackstagePass_h__ + +#include "js/loader/ModuleLoaderBase.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/StorageAccess.h" +#include "nsISupports.h" +#include "nsWeakReference.h" +#include "nsIGlobalObject.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIXPCScriptable.h" + +#include "js/HeapAPI.h" + +class XPCWrappedNative; + +class BackstagePass final : public nsIGlobalObject, + public nsIScriptObjectPrincipal, + public nsIXPCScriptable, + public nsIClassInfo, + public nsSupportsWeakReference { + public: + BackstagePass(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + using ModuleLoaderBase = JS::loader::ModuleLoaderBase; + + nsIPrincipal* GetPrincipal() override { return mPrincipal; } + + nsIPrincipal* GetEffectiveCookiePrincipal() override { return mPrincipal; } + + nsIPrincipal* GetEffectiveStoragePrincipal() override { return mPrincipal; } + + nsIPrincipal* PartitionedPrincipal() override { return mPrincipal; } + + mozilla::OriginTrials Trials() const override { return {}; } + + JSObject* GetGlobalJSObject() override; + JSObject* GetGlobalJSObjectPreserveColor() const override; + + ModuleLoaderBase* GetModuleLoader(JSContext* aCx) override { + return mModuleLoader; + } + + mozilla::StorageAccess GetStorageAccess() final { + MOZ_ASSERT(NS_IsMainThread()); + return mozilla::StorageAccess::eAllow; + } + + mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> GetStorageKey() + override; + + void ForgetGlobalObject() { mWrapper = nullptr; } + + void SetGlobalObject(JSObject* global); + + void InitModuleLoader(ModuleLoaderBase* aModuleLoader) { + MOZ_ASSERT(!mModuleLoader); + mModuleLoader = aModuleLoader; + } + + bool ShouldResistFingerprinting( + RFPTarget aTarget = RFPTarget::Unknown) const override { + // BackstagePass is always the System Principal + MOZ_RELEASE_ASSERT(mPrincipal->IsSystemPrincipal()); + return false; + } + + private: + virtual ~BackstagePass() = default; + + nsCOMPtr<nsIPrincipal> mPrincipal; + XPCWrappedNative* mWrapper; + + RefPtr<JS::loader::ModuleLoaderBase> mModuleLoader; +}; + +#endif // BackstagePass_h__ diff --git a/js/xpconnect/src/ExportHelpers.cpp b/js/xpconnect/src/ExportHelpers.cpp new file mode 100644 index 0000000000..ee89547b4d --- /dev/null +++ b/js/xpconnect/src/ExportHelpers.cpp @@ -0,0 +1,598 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "WrapperFactory.h" +#include "AccessCheck.h" +#include "jsfriendapi.h" +#include "js/CallAndConstruct.h" // JS::Call, JS::Construct, JS::IsCallable +#include "js/Exception.h" +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById +#include "js/Proxy.h" +#include "js/Wrapper.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsJSUtils.h" +#include "js/Object.h" // JS::GetCompartment + +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +namespace xpc { + +bool IsReflector(JSObject* obj, JSContext* cx) { + obj = js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false); + if (!obj) { + return false; + } + return IsWrappedNativeReflector(obj) || dom::IsDOMObject(obj); +} + +enum StackScopedCloneTags : uint32_t { + SCTAG_BASE = JS_SCTAG_USER_MIN, + SCTAG_REFLECTOR, + SCTAG_BLOB, + SCTAG_FUNCTION, +}; + +class MOZ_STACK_CLASS StackScopedCloneData : public StructuredCloneHolderBase { + public: + StackScopedCloneData(JSContext* aCx, StackScopedCloneOptions* aOptions) + : mOptions(aOptions), mReflectors(aCx), mFunctions(aCx) {} + + ~StackScopedCloneData() { Clear(); } + + JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader, + const JS::CloneDataPolicy& aCloneDataPolicy, + uint32_t aTag, uint32_t aData) override { + if (aTag == SCTAG_REFLECTOR) { + MOZ_ASSERT(!aData); + + size_t idx; + if (!JS_ReadBytes(aReader, &idx, sizeof(size_t))) { + return nullptr; + } + + RootedObject reflector(aCx, mReflectors[idx]); + MOZ_ASSERT(reflector, "No object pointer?"); + MOZ_ASSERT(IsReflector(reflector, aCx), + "Object pointer must be a reflector!"); + + if (!JS_WrapObject(aCx, &reflector)) { + return nullptr; + } + + return reflector; + } + + if (aTag == SCTAG_FUNCTION) { + MOZ_ASSERT(aData < mFunctions.length()); + + RootedValue functionValue(aCx); + RootedObject obj(aCx, mFunctions[aData]); + + if (!JS_WrapObject(aCx, &obj)) { + return nullptr; + } + + FunctionForwarderOptions forwarderOptions; + if (!xpc::NewFunctionForwarder(aCx, JS::VoidHandlePropertyKey, obj, + forwarderOptions, &functionValue)) { + return nullptr; + } + + return &functionValue.toObject(); + } + + if (aTag == SCTAG_BLOB) { + MOZ_ASSERT(!aData); + + size_t idx; + if (!JS_ReadBytes(aReader, &idx, sizeof(size_t))) { + return nullptr; + } + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + MOZ_ASSERT(global); + + // RefPtr<File> needs to go out of scope before toObjectOrNull() is called + // because otherwise the static analysis thinks it can gc the JSObject via + // the stack. + JS::Rooted<JS::Value> val(aCx); + { + RefPtr<Blob> blob = Blob::Create(global, mBlobImpls[idx]); + if (NS_WARN_IF(!blob)) { + return nullptr; + } + + if (!ToJSValue(aCx, blob, &val)) { + return nullptr; + } + } + + return val.toObjectOrNull(); + } + + MOZ_ASSERT_UNREACHABLE("Encountered garbage in the clone stream!"); + return nullptr; + } + + bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter, + JS::Handle<JSObject*> aObj, + bool* aSameProcessScopeRequired) override { + { + JS::Rooted<JSObject*> obj(aCx, aObj); + Blob* blob = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) { + BlobImpl* blobImpl = blob->Impl(); + MOZ_ASSERT(blobImpl); + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mBlobImpls.AppendElement(blobImpl); + + size_t idx = mBlobImpls.Length() - 1; + return JS_WriteUint32Pair(aWriter, SCTAG_BLOB, 0) && + JS_WriteBytes(aWriter, &idx, sizeof(size_t)); + } + } + + if (mOptions->wrapReflectors && IsReflector(aObj, aCx)) { + if (!mReflectors.append(aObj)) { + return false; + } + + size_t idx = mReflectors.length() - 1; + if (!JS_WriteUint32Pair(aWriter, SCTAG_REFLECTOR, 0)) { + return false; + } + if (!JS_WriteBytes(aWriter, &idx, sizeof(size_t))) { + return false; + } + return true; + } + + if (JS::IsCallable(aObj)) { + if (mOptions->cloneFunctions) { + if (!mFunctions.append(aObj)) { + return false; + } + return JS_WriteUint32Pair(aWriter, SCTAG_FUNCTION, + mFunctions.length() - 1); + } else { + JS_ReportErrorASCII( + aCx, "Permission denied to pass a Function via structured clone"); + return false; + } + } + + JS_ReportErrorASCII(aCx, + "Encountered unsupported value type writing " + "stack-scoped structured clone"); + return false; + } + + StackScopedCloneOptions* mOptions; + RootedObjectVector mReflectors; + RootedObjectVector mFunctions; + nsTArray<RefPtr<BlobImpl>> mBlobImpls; +}; + +/* + * General-purpose structured-cloning utility for cases where the structured + * clone buffer is only used in stack-scope (that is to say, the buffer does + * not escape from this function). The stack-scoping allows us to pass + * references to various JSObjects directly in certain situations without + * worrying about lifetime issues. + * + * This function assumes that |cx| is already entered the compartment we want + * to clone to, and that |val| may not be same-compartment with cx. When the + * function returns, |val| is set to the result of the clone. + */ +bool StackScopedClone(JSContext* cx, StackScopedCloneOptions& options, + HandleObject sourceScope, MutableHandleValue val) { + StackScopedCloneData data(cx, &options); + { + // For parsing val we have to enter (a realm in) its compartment. + JSAutoRealm ar(cx, sourceScope); + if (!data.Write(cx, val)) { + return false; + } + } + + // Now recreate the clones in the target realm. + if (!data.Read(cx, val)) { + return false; + } + + // Deep-freeze if requested. + if (options.deepFreeze && val.isObject()) { + RootedObject obj(cx, &val.toObject()); + if (!JS_DeepFreezeObject(cx, obj)) { + return false; + } + } + + return true; +} + +// Note - This function mirrors the logic of CheckPassToChrome in +// ChromeObjectWrapper.cpp. +static bool CheckSameOriginArg(JSContext* cx, FunctionForwarderOptions& options, + HandleValue v) { + // Consumers can explicitly opt out of this security check. This is used in + // the web console to allow the utility functions to accept cross-origin + // Windows. + if (options.allowCrossOriginArguments) { + return true; + } + + // Primitives are fine. + if (!v.isObject()) { + return true; + } + RootedObject obj(cx, &v.toObject()); + MOZ_ASSERT(JS::GetCompartment(obj) != js::GetContextCompartment(cx), + "This should be invoked after entering the compartment but before " + "wrapping the values"); + + // Non-wrappers are fine. + if (!js::IsWrapper(obj)) { + return true; + } + + // Wrappers leading back to the scope of the exported function are fine. + if (JS::GetCompartment(js::UncheckedUnwrap(obj)) == + js::GetContextCompartment(cx)) { + return true; + } + + // Same-origin wrappers are fine. + if (AccessCheck::wrapperSubsumes(obj)) { + return true; + } + + // Badness. + JS_ReportErrorASCII(cx, + "Permission denied to pass object to exported function"); + return false; +} + +// Sanitize the exception on cx (which comes from calling unwrappedFun), if the +// current Realm of cx shouldn't have access to it. unwrappedFun is generally +// _not_ in the current Realm of cx here. +static void MaybeSanitizeException(JSContext* cx, + JS::Handle<JSObject*> unwrappedFun) { + // Ensure that we are not propagating more-privileged exceptions + // to less-privileged code. + nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal(cx); + + // No need to sanitize uncatchable exceptions, just return. + if (!JS_IsExceptionPending(cx)) { + return; + } + + // Re-enter the unwrappedFun Realm to do get the current exception, so we + // don't end up unnecessarily wrapping exceptions. + { // Scope for JSAutoRealm + JSAutoRealm ar(cx, unwrappedFun); + + JS::ExceptionStack exnStack(cx); + + // If JS::GetPendingExceptionStack returns false, we somehow failed to wrap + // the exception into our compartment. It seems fine to treat this as an + // uncatchable exception by returning without setting any exception on the + // JS context. + if (!JS::GetPendingExceptionStack(cx, &exnStack)) { + JS_ClearPendingException(cx); + return; + } + + // Let through non-objects as-is, because some APIs rely on + // that and accidental exceptions are never non-objects. + if (!exnStack.exception().isObject() || + callerPrincipal->Subsumes(nsContentUtils::ObjectPrincipal( + js::UncheckedUnwrap(&exnStack.exception().toObject())))) { + // Just leave exn as-is. + return; + } + + // Whoever we are throwing the exception to should not have access to + // the exception. Sanitize it. First clear the existing exception. + JS_ClearPendingException(cx); + { // Scope for AutoJSAPI + AutoJSAPI jsapi; + if (jsapi.Init(unwrappedFun)) { + JS::SetPendingExceptionStack(cx, exnStack); + } + // If Init() fails, we can't report the exception, but oh, well. + + // Now just let the AutoJSAPI go out of scope and it will report the + // exception in its destructor. + } + } + + // Now back in our original Realm again, throw a sanitized exception. + ErrorResult rv; + rv.ThrowInvalidStateError("An exception was thrown"); + // Can we provide a better context here? + Unused << rv.MaybeSetPendingException(cx); +} + +static bool FunctionForwarder(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Grab the options from the reserved slot. + RootedObject optionsObj( + cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject()); + FunctionForwarderOptions options(cx, optionsObj); + if (!options.Parse()) { + return false; + } + + // Grab and unwrap the underlying callable. + RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); + RootedObject unwrappedFun(cx, js::UncheckedUnwrap(&v.toObject())); + + RootedValue thisVal(cx, NullValue()); + if (!args.isConstructing()) { + RootedObject thisObject(cx); + if (!args.computeThis(cx, &thisObject)) { + return false; + } + thisVal.setObject(*thisObject); + } + + bool ok = true; + { + // We manually implement the contents of CrossCompartmentWrapper::call + // here, because certain function wrappers (notably content->nsEP) are + // not callable. + JSAutoRealm ar(cx, unwrappedFun); + bool crossCompartment = + JS::GetCompartment(unwrappedFun) != JS::GetCompartment(&args.callee()); + if (crossCompartment) { + if (!CheckSameOriginArg(cx, options, thisVal) || + !JS_WrapValue(cx, &thisVal)) { + return false; + } + + for (size_t n = 0; n < args.length(); ++n) { + if (!CheckSameOriginArg(cx, options, args[n]) || + !JS_WrapValue(cx, args[n])) { + return false; + } + } + } + + RootedValue fval(cx, ObjectValue(*unwrappedFun)); + if (args.isConstructing()) { + RootedObject obj(cx); + ok = JS::Construct(cx, fval, args, &obj); + if (ok) { + args.rval().setObject(*obj); + } + } else { + ok = JS::Call(cx, thisVal, fval, args, args.rval()); + } + } + + // Now that we are back in our original Realm, we can check whether to + // sanitize the exception. + if (!ok) { + MaybeSanitizeException(cx, unwrappedFun); + return false; + } + + // Rewrap the return value into our compartment. + return JS_WrapValue(cx, args.rval()); +} + +bool NewFunctionForwarder(JSContext* cx, HandleId idArg, HandleObject callable, + FunctionForwarderOptions& options, + MutableHandleValue vp) { + RootedId id(cx, idArg); + if (id.isVoid()) { + id = GetJSIDByIndex(cx, XPCJSContext::IDX_EMPTYSTRING); + } + + // If our callable is a (possibly wrapped) function, we can give + // the exported thing the right number of args. + unsigned nargs = 0; + RootedObject unwrapped(cx, js::UncheckedUnwrap(callable)); + if (unwrapped) { + if (JSFunction* fun = JS_GetObjectFunction(unwrapped)) { + nargs = JS_GetFunctionArity(fun); + } + } + + // We have no way of knowing whether the underlying function wants to be a + // constructor or not, so we just mark all forwarders as constructors, and + // let the underlying function throw for construct calls if it wants. + JSFunction* fun = js::NewFunctionByIdWithReserved( + cx, FunctionForwarder, nargs, JSFUN_CONSTRUCTOR, id); + if (!fun) { + return false; + } + + // Stash the callable in slot 0. + AssertSameCompartment(cx, callable); + RootedObject funobj(cx, JS_GetFunctionObject(fun)); + js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); + + // Stash the options in slot 1. + RootedObject optionsObj(cx, options.ToJSObject(cx)); + if (!optionsObj) { + return false; + } + js::SetFunctionNativeReserved(funobj, 1, ObjectValue(*optionsObj)); + + vp.setObject(*funobj); + return true; +} + +bool ExportFunction(JSContext* cx, HandleValue vfunction, HandleValue vscope, + HandleValue voptions, MutableHandleValue rval) { + bool hasOptions = !voptions.isUndefined(); + if (!vscope.isObject() || !vfunction.isObject() || + (hasOptions && !voptions.isObject())) { + JS_ReportErrorASCII(cx, "Invalid argument"); + return false; + } + + RootedObject funObj(cx, &vfunction.toObject()); + RootedObject targetScope(cx, &vscope.toObject()); + ExportFunctionOptions options(cx, + hasOptions ? &voptions.toObject() : nullptr); + if (hasOptions && !options.Parse()) { + return false; + } + + // Restrictions: + // * We must subsume the scope we are exporting to. + // * We must subsume the function being exported, because the function + // forwarder manually circumvents security wrapper CALL restrictions. + targetScope = js::CheckedUnwrapDynamic(targetScope, cx); + // For the function we can just CheckedUnwrapStatic, because if it's + // not callable we're going to fail out anyway. + funObj = js::CheckedUnwrapStatic(funObj); + if (!targetScope || !funObj) { + JS_ReportErrorASCII(cx, "Permission denied to export function into scope"); + return false; + } + + if (js::IsScriptedProxy(targetScope)) { + JS_ReportErrorASCII(cx, "Defining property on proxy object is not allowed"); + return false; + } + + { + // We need to operate in the target scope from here on, let's enter + // its realm. + JSAutoRealm ar(cx, targetScope); + + // Unwrapping to see if we have a callable. + funObj = UncheckedUnwrap(funObj); + if (!JS::IsCallable(funObj)) { + JS_ReportErrorASCII(cx, "First argument must be a function"); + return false; + } + + RootedId id(cx, options.defineAs); + if (id.isVoid()) { + // If there wasn't any function name specified, copy the name from the + // function being imported. But be careful in case the callable we have + // is not actually a JSFunction. + RootedString funName(cx); + JSFunction* fun = JS_GetObjectFunction(funObj); + if (fun) { + funName = JS_GetFunctionId(fun); + } + if (!funName) { + funName = JS_AtomizeAndPinString(cx, ""); + } + JS_MarkCrossZoneIdValue(cx, StringValue(funName)); + + if (!JS_StringToId(cx, funName, &id)) { + return false; + } + } else { + JS_MarkCrossZoneId(cx, id); + } + MOZ_ASSERT(id.isString()); + + // The function forwarder will live in the target compartment. Since + // this function will be referenced from its private slot, to avoid a + // GC hazard, we must wrap it to the same compartment. + if (!JS_WrapObject(cx, &funObj)) { + return false; + } + + // And now, let's create the forwarder function in the target compartment + // for the function the be exported. + FunctionForwarderOptions forwarderOptions; + forwarderOptions.allowCrossOriginArguments = + options.allowCrossOriginArguments; + if (!NewFunctionForwarder(cx, id, funObj, forwarderOptions, rval)) { + JS_ReportErrorASCII(cx, "Exporting function failed"); + return false; + } + + // We have the forwarder function in the target compartment. If + // defineAs was set, we also need to define it as a property on + // the target. + if (!options.defineAs.isVoid()) { + if (!JS_DefinePropertyById(cx, targetScope, id, rval, JSPROP_ENUMERATE)) { + return false; + } + } + } + + // Finally we have to re-wrap the exported function back to the caller + // compartment. + if (!JS_WrapValue(cx, rval)) { + return false; + } + + return true; +} + +bool CreateObjectIn(JSContext* cx, HandleValue vobj, + CreateObjectInOptions& options, MutableHandleValue rval) { + if (!vobj.isObject()) { + JS_ReportErrorASCII(cx, "Expected an object as the target scope"); + return false; + } + + // cx represents the caller Realm. + RootedObject scope(cx, js::CheckedUnwrapDynamic(&vobj.toObject(), cx)); + if (!scope) { + JS_ReportErrorASCII( + cx, "Permission denied to create object in the target scope"); + return false; + } + + bool define = !options.defineAs.isVoid(); + + if (define && js::IsScriptedProxy(scope)) { + JS_ReportErrorASCII(cx, "Defining property on proxy object is not allowed"); + return false; + } + + RootedObject obj(cx); + { + JSAutoRealm ar(cx, scope); + JS_MarkCrossZoneId(cx, options.defineAs); + + obj = JS_NewPlainObject(cx); + if (!obj) { + return false; + } + + if (define) { + if (!JS_DefinePropertyById(cx, scope, options.defineAs, obj, + JSPROP_ENUMERATE)) + return false; + } + } + + rval.setObject(*obj); + if (!WrapperFactory::WaiveXrayAndWrap(cx, rval)) { + return false; + } + + return true; +} + +} /* namespace xpc */ diff --git a/js/xpconnect/src/JSServices.cpp b/js/xpconnect/src/JSServices.cpp new file mode 100644 index 0000000000..cb8fe6cdca --- /dev/null +++ b/js/xpconnect/src/JSServices.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "StaticComponents.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ProfilerLabels.h" +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById +#include "js/String.h" // JS::LinearStringHasLatin1Chars +#include "nsJSUtils.h" + +using namespace mozilla; +using namespace JS; + +namespace xpc { + +static bool Services_NewEnumerate(JSContext* cx, HandleObject obj, + MutableHandleIdVector properties, + bool enumerableOnly); +static bool Services_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp); +static bool Services_MayResolve(const JSAtomState& names, jsid id, + JSObject* maybeObj); + +static const JSClassOps sServices_ClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + Services_NewEnumerate, // newEnumerate + Services_Resolve, // resolve + Services_MayResolve, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +static const JSClass sServices_Class = {"JSServices", 0, &sServices_ClassOps}; + +JSObject* NewJSServices(JSContext* cx) { + return JS_NewObject(cx, &sServices_Class); +} + +static bool Services_NewEnumerate(JSContext* cx, HandleObject obj, + MutableHandleIdVector properties, + bool enumerableOnly) { + auto services = xpcom::StaticComponents::GetJSServices(); + + if (!properties.reserve(services.Length())) { + JS_ReportOutOfMemory(cx); + return false; + } + + RootedId id(cx); + RootedString name(cx); + for (const auto& service : services) { + name = JS_AtomizeString(cx, service.Name().get()); + if (!name || !JS_StringToId(cx, name, &id)) { + return false; + } + properties.infallibleAppend(id); + } + + return true; +} + +static JSLinearString* GetNameIfLatin1(jsid id) { + if (id.isString()) { + JSLinearString* name = id.toLinearString(); + if (JS::LinearStringHasLatin1Chars(name)) { + return name; + } + } + return nullptr; +} + +static bool GetServiceImpl(JSContext* cx, const xpcom::JSServiceEntry& service, + JS::MutableHandleObject aObj, ErrorResult& aRv) { + nsresult rv; + nsCOMPtr<nsISupports> inst = service.Module().GetService(&rv); + if (!inst) { + aRv.Throw(rv); + return false; + } + + auto ifaces = service.Interfaces(); + + if (ifaces.Length() == 0) { + // If we weren't given any interfaces, we're expecting either a WebIDL + // object or a wrapped JS object. In the former case, the object will handle + // its own wrapping, and there's nothing to do. In the latter case, we want + // to unwrap the underlying JS object. + if (nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(inst)) { + aObj.set(wrappedJS->GetJSObject()); + return !!aObj; + } + } + + JS::RootedValue val(cx); + + const nsIID* iid = ifaces.Length() ? ifaces[0] : nullptr; + xpcObjectHelper helper(inst); + if (!XPCConvert::NativeInterface2JSObject(cx, &val, helper, iid, + /* allowNativeWrapper */ true, + &rv)) { + aRv.Throw(rv); + return false; + } + + if (ifaces.Length() > 1) { + auto* wn = XPCWrappedNative::Get(&val.toObject()); + for (const nsIID* iid : Span(ifaces).From(1)) { + // Ignore any supplemental interfaces that aren't implemented. Tests do + // weird things with some services, and JS can generally handle the + // interfaces being absent. + Unused << wn->FindTearOff(cx, *iid); + } + } + + aObj.set(&val.toObject()); + return true; +} + +static JSObject* GetService(JSContext* cx, const xpcom::JSServiceEntry& service, + ErrorResult& aRv) { + JS::RootedObject obj(cx); + if (!GetServiceImpl(cx, service, &obj, aRv)) { + return nullptr; + } + return obj; +} + +static bool Services_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp) { + *resolvedp = false; + JSLinearString* name = GetNameIfLatin1(id); + if (!name) { + return true; + } + + nsAutoJSLinearCString nameStr(name); + if (const auto* service = xpcom::JSServiceEntry::Lookup(nameStr)) { + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Services_Resolve", + OTHER, service->Name()); + *resolvedp = true; + + ErrorResult rv; + JS::RootedValue val(cx); + + val.setObjectOrNull(GetService(cx, *service, rv)); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + return JS_DefinePropertyById(cx, obj, id, val, JSPROP_ENUMERATE); + } + return true; +} + +static bool Services_MayResolve(const JSAtomState& names, jsid id, + JSObject* maybeObj) { + if (JSLinearString* name = GetNameIfLatin1(id)) { + nsAutoJSLinearCString nameStr(name); + return xpcom::JSServiceEntry::Lookup(nameStr); + } + return false; +} + +} // namespace xpc diff --git a/js/xpconnect/src/JSServices.h b/js/xpconnect/src/JSServices.h new file mode 100644 index 0000000000..9dcfd15fdb --- /dev/null +++ b/js/xpconnect/src/JSServices.h @@ -0,0 +1,18 @@ +/* -*- 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/. */ + +#ifndef JSServices_h +#define JSServices_h + +#include "jstypes.h" + +namespace xpc { + +JSObject* NewJSServices(JSContext* cx); + +} + +#endif // ifndef JSServices_h diff --git a/js/xpconnect/src/README b/js/xpconnect/src/README new file mode 100644 index 0000000000..260eed6bcd --- /dev/null +++ b/js/xpconnect/src/README @@ -0,0 +1,3 @@ + +see http://www.mozilla.org/scriptable + diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp new file mode 100644 index 0000000000..b75bebc6f1 --- /dev/null +++ b/js/xpconnect/src/Sandbox.cpp @@ -0,0 +1,2256 @@ +/* -*- 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/. */ + +/* + * The Components.Sandbox object. + */ + +#include "AccessCheck.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable +#include "js/CharacterEncoding.h" +#include "js/CompilationAndEvaluation.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineFunction, JS_DefineFunctions, JS_DefineProperty, JS_GetElement, JS_GetProperty, JS_HasProperty, JS_SetProperty, JS_SetPropertyById +#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById, JS_GetPropertyDescriptorById +#include "js/PropertySpec.h" +#include "js/Proxy.h" +#include "js/SourceText.h" +#include "js/StructuredClone.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsIException.h" // for nsIStackFrame +#include "nsIScriptContext.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIURI.h" +#include "nsJSUtils.h" +#include "nsNetUtil.h" +#include "ExpandedPrincipal.h" +#include "WrapperFactory.h" +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "XPCWrapper.h" +#include "Crypto.h" +#include "mozilla/Result.h" +#include "mozilla/dom/AbortControllerBinding.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/BindingCallContext.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/cache/CacheStorage.h" +#include "mozilla/dom/CSSBinding.h" +#include "mozilla/dom/CSSRuleBinding.h" +#include "mozilla/dom/DirectoryBinding.h" +#include "mozilla/dom/DocumentBinding.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/DOMParserBinding.h" +#include "mozilla/dom/DOMTokenListBinding.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FileBinding.h" +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/IOUtilsBinding.h" +#include "mozilla/dom/InspectorUtilsBinding.h" +#include "mozilla/dom/MessageChannelBinding.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/MIDIInputMapBinding.h" +#include "mozilla/dom/MIDIOutputMapBinding.h" +#include "mozilla/dom/ModuleLoader.h" +#include "mozilla/dom/NodeBinding.h" +#include "mozilla/dom/NodeFilterBinding.h" +#include "mozilla/dom/PathUtilsBinding.h" +#include "mozilla/dom/PerformanceBinding.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebuggingBinding.h" +#include "mozilla/dom/RangeBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ReadableStreamBinding.h" +#include "mozilla/dom/ResponseBinding.h" +#ifdef MOZ_WEBRTC +# include "mozilla/dom/RTCIdentityProviderRegistrar.h" +#endif +#include "mozilla/dom/FileReaderBinding.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SelectionBinding.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/dom/TextDecoderBinding.h" +#include "mozilla/dom/TextEncoderBinding.h" +#include "mozilla/dom/URLBinding.h" +#include "mozilla/dom/URLSearchParamsBinding.h" +#include "mozilla/dom/XMLHttpRequest.h" +#include "mozilla/dom/WebSocketBinding.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/XMLSerializerBinding.h" +#include "mozilla/dom/FormDataBinding.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/Maybe.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_extensions.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; +using namespace JS::loader; +using namespace xpc; + +using mozilla::dom::DestroyProtoAndIfaceCache; +using mozilla::dom::IndexedDatabaseManager; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SandboxPrivate) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR + NS_IMPL_CYCLE_COLLECTION_UNLINK(mModuleLoader) + tmp->UnlinkObjectsInGlobal(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mModuleLoader) + tmp->TraverseObjectsInGlobal(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, + public nsIXPCScriptable { + public: + // Aren't macros nice? + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX + NS_DECL_NSIXPCSCRIPTABLE + + public: + nsXPCComponents_utils_Sandbox(); + + private: + virtual ~nsXPCComponents_utils_Sandbox(); + + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +already_AddRefed<nsIXPCComponents_utils_Sandbox> xpc::NewSandboxConstructor() { + nsCOMPtr<nsIXPCComponents_utils_Sandbox> sbConstructor = + new nsXPCComponents_utils_Sandbox(); + return sbConstructor.forget(); +} + +static bool SandboxDump(JSContext* cx, unsigned argc, Value* vp) { + if (!nsJSUtils::DumpEnabled()) { + return true; + } + + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) { + return true; + } + + RootedString str(cx, ToString(cx, args[0])); + if (!str) { + return false; + } + + JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str); + char* cstr = utf8str.get(); + if (!cstr) { + return false; + } + +#if defined(XP_MACOSX) + // Be nice and convert all \r to \n. + char* c = cstr; + char* cEnd = cstr + strlen(cstr); + while (c < cEnd) { + if (*c == '\r') { + *c = '\n'; + } + c++; + } +#endif + MOZ_LOG(nsContentUtils::DOMDumpLog(), mozilla::LogLevel::Debug, + ("[Sandbox.Dump] %s", cstr)); +#ifdef ANDROID + __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr); +#endif + fputs(cstr, stdout); + fflush(stdout); + args.rval().setBoolean(true); + return true; +} + +static bool SandboxDebug(JSContext* cx, unsigned argc, Value* vp) { +#ifdef DEBUG + return SandboxDump(cx, argc, vp); +#else + return true; +#endif +} + +static bool SandboxImport(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1 || args[0].isPrimitive()) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + RootedString funname(cx); + if (args.length() > 1) { + // Use the second parameter as the function name. + funname = ToString(cx, args[1]); + if (!funname) { + return false; + } + } else { + // NB: funobj must only be used to get the JSFunction out. + RootedObject funobj(cx, &args[0].toObject()); + if (js::IsProxy(funobj)) { + funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj); + } + + JSAutoRealm ar(cx, funobj); + + RootedValue funval(cx, ObjectValue(*funobj)); + JSFunction* fun = JS_ValueToFunction(cx, funval); + if (!fun) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + // Use the actual function name as the name. + funname = JS_GetFunctionId(fun); + if (!funname) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + } + JS_MarkCrossZoneIdValue(cx, StringValue(funname)); + + RootedId id(cx); + if (!JS_StringToId(cx, funname, &id)) { + return false; + } + + // We need to resolve the this object, because this function is used + // unbound and should still work and act on the original sandbox. + + RootedObject thisObject(cx); + if (!args.computeThis(cx, &thisObject)) { + return false; + } + + if (!JS_SetPropertyById(cx, thisObject, id, args[0])) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +bool xpc::SandboxCreateCrypto(JSContext* cx, JS::Handle<JSObject*> obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsIGlobalObject* native = xpc::NativeGlobal(obj); + MOZ_ASSERT(native); + + dom::Crypto* crypto = new dom::Crypto(native); + JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE); +} + +#ifdef MOZ_WEBRTC +static bool SandboxCreateRTCIdentityProvider(JSContext* cx, + JS::HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsCOMPtr<nsIGlobalObject> nativeGlobal = xpc::NativeGlobal(obj); + MOZ_ASSERT(nativeGlobal); + + dom::RTCIdentityProviderRegistrar* registrar = + new dom::RTCIdentityProviderRegistrar(nativeGlobal); + JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped, + JSPROP_ENUMERATE); +} +#endif + +static bool SandboxFetch(JSContext* cx, JS::HandleObject scope, + const CallArgs& args) { + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "fetch requires at least 1 argument"); + return false; + } + + BindingCallContext callCx(cx, "fetch"); + RequestOrUSVString request; + if (!request.Init(callCx, args[0], "Argument 1")) { + return false; + } + RootedDictionary<dom::RequestInit> options(cx); + if (!options.Init(callCx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2", false)) { + return false; + } + nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope); + if (!global) { + return false; + } + dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx) + ? dom::CallerType::System + : dom::CallerType::NonSystem; + ErrorResult rv; + RefPtr<dom::Promise> response = FetchRequest( + global, Constify(request), Constify(options), callerType, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + args.rval().setObject(*response->PromiseObj()); + return true; +} + +static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); + if (SandboxFetch(cx, scope, args)) { + return true; + } + return ConvertExceptionToPromise(cx, args.rval()); +} + +bool xpc::SandboxCreateFetch(JSContext* cx, JS::Handle<JSObject*> obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) && + dom::Request_Binding::GetConstructorObject(cx) && + dom::Response_Binding::GetConstructorObject(cx) && + dom::Headers_Binding::GetConstructorObject(cx); +} + +static bool SandboxCreateStorage(JSContext* cx, JS::HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsIGlobalObject* native = xpc::NativeGlobal(obj); + MOZ_ASSERT(native); + + dom::StorageManager* storageManager = new dom::StorageManager(native); + JS::RootedObject wrapped(cx, storageManager->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "storage", wrapped, JSPROP_ENUMERATE); +} + +static bool SandboxStructuredClone(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "structuredClone", 1)) { + return false; + } + + RootedDictionary<dom::StructuredSerializeOptions> options(cx); + BindingCallContext callCx(cx, "structuredClone"); + if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2", false)) { + return false; + } + + nsIGlobalObject* global = CurrentNativeGlobal(cx); + if (!global) { + JS_ReportErrorASCII(cx, "structuredClone: Missing global"); + return false; + } + + JS::Rooted<JS::Value> result(cx); + ErrorResult rv; + nsContentUtils::StructuredClone(cx, global, args[0], options, &result, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + MOZ_ASSERT_IF(result.isGCThing(), + !JS::GCThingIsMarkedGray(result.toGCCellPtr())); + args.rval().set(result); + return true; +} + +bool xpc::SandboxCreateStructuredClone(JSContext* cx, HandleObject obj) { + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + return JS_DefineFunction(cx, obj, "structuredClone", SandboxStructuredClone, + 1, 0); +} + +static bool SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); + return false; + } + if (!args[0].isObject()) { + args.rval().setBoolean(false); + return true; + } + + RootedObject obj(cx, &args[0].toObject()); + // CheckedUnwrapStatic is OK here, since we only care about whether + // it's a scripted proxy and the things CheckedUnwrapStatic fails on + // are not. + obj = js::CheckedUnwrapStatic(obj); + if (!obj) { + args.rval().setBoolean(false); + return true; + } + + args.rval().setBoolean(js::IsScriptedProxy(obj)); + return true; +} + +/* + * Expected type of the arguments and the return value: + * function exportFunction(function funToExport, + * object targetScope, + * [optional] object options) + */ +static bool SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2) { + JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); + return false; + } + + RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); + return ExportFunction(cx, args[0], args[1], options, args.rval()); +} + +static bool SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); + return false; + } + + RootedObject optionsObj(cx); + bool calledWithOptions = args.length() > 1; + if (calledWithOptions) { + if (!args[1].isObject()) { + JS_ReportErrorASCII( + cx, "Expected the 2nd argument (options) to be an object"); + return false; + } + optionsObj = &args[1].toObject(); + } + + CreateObjectInOptions options(cx, optionsObj); + if (calledWithOptions && !options.Parse()) { + return false; + } + + return xpc::CreateObjectIn(cx, args[0], options, args.rval()); +} + +static bool SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2) { + JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); + return false; + } + + RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); + return xpc::CloneInto(cx, args[0], args[1], options, args.rval()); +} + +static void sandbox_finalize(JS::GCContext* gcx, JSObject* obj) { + SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj); + if (!priv) { + // priv can be null if CreateSandboxObject fails in the middle. + return; + } + + priv->ForgetGlobalObject(obj); + DestroyProtoAndIfaceCache(obj); + DeferredFinalize(static_cast<nsIScriptObjectPrincipal*>(priv)); +} + +static size_t sandbox_moved(JSObject* obj, JSObject* old) { + // Note that this hook can be called before the private pointer is set. In + // this case the SandboxPrivate will not exist yet, so there is nothing to + // do. + SandboxPrivate* priv = SandboxPrivate::GetPrivate(obj); + if (!priv) { + return 0; + } + + return priv->ObjectMoved(obj, old); +} + +#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT \ + (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET) + +static const JSClassOps SandboxClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + JS_NewEnumerateStandardClasses, // newEnumerate + JS_ResolveStandardClass, // resolve + JS_MayResolveStandardClass, // mayResolve + sandbox_finalize, // finalize + nullptr, // call + nullptr, // construct + JS_GlobalObjectTraceHook, // trace +}; + +static const js::ClassExtension SandboxClassExtension = { + sandbox_moved, // objectMovedOp +}; + +static const JSClass SandboxClass = { + "Sandbox", + XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE, + &SandboxClassOps, + JS_NULL_CLASS_SPEC, + &SandboxClassExtension, + JS_NULL_OBJECT_OPS}; + +static const JSFunctionSpec SandboxFunctions[] = { + JS_FN("dump", SandboxDump, 1, 0), JS_FN("debug", SandboxDebug, 1, 0), + JS_FN("importFunction", SandboxImport, 1, 0), JS_FS_END}; + +bool xpc::IsSandbox(JSObject* obj) { + const JSClass* clasp = JS::GetClass(obj); + return clasp == &SandboxClass; +} + +/***************************************************************************/ +nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() = default; + +nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() = default; + +NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox, + nsIXPCComponents_utils_Sandbox, nsIXPCScriptable) + +NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) +NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) + +// We use the nsIXPScriptable macros to generate lots of stuff for us. +#define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" +#define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT) +#include "xpc_map_end.h" /* This #undef's the above. */ + +class SandboxProxyHandler : public js::Wrapper { + public: + constexpr SandboxProxyHandler() : js::Wrapper(0) {} + + virtual bool getOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override; + + // We just forward the high-level methods to the BaseProxyHandler versions + // which implement them in terms of lower-level methods. + virtual bool has(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const override; + virtual bool get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::HandleValue receiver, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const override; + virtual bool set(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<JS::Value> v, + JS::Handle<JS::Value> receiver, + JS::ObjectOpResult& result) const override; + + virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys( + JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleIdVector props) const override; + virtual bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleIdVector props) const override; + + private: + // Implements the custom getPropertyDescriptor behavior. If the getOwn + // argument is true we only look for "own" properties. + bool getPropertyDescriptorImpl( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + bool getOwn, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const; +}; + +static const SandboxProxyHandler sandboxProxyHandler; + +namespace xpc { + +bool IsSandboxPrototypeProxy(JSObject* obj) { + return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler; +} + +bool IsWebExtensionContentScriptSandbox(JSObject* obj) { + return IsSandbox(obj) && + CompartmentPrivate::Get(obj)->isWebExtensionContentScript; +} + +} // namespace xpc + +// A proxy handler that lets us wrap callables and invoke them with +// the correct this object, while forwarding all other operations down +// to them directly. +class SandboxCallableProxyHandler : public js::Wrapper { + public: + constexpr SandboxCallableProxyHandler() : js::Wrapper(0) {} + + virtual bool call(JSContext* cx, JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const override; + + static const size_t SandboxProxySlot = 0; + + static inline JSObject* getSandboxProxy(JS::Handle<JSObject*> proxy) { + return &js::GetProxyReservedSlot(proxy, SandboxProxySlot).toObject(); + } +}; + +static const SandboxCallableProxyHandler sandboxCallableProxyHandler; + +bool SandboxCallableProxyHandler::call(JSContext* cx, + JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const { + // We forward the call to our underlying callable. + + // Get our SandboxProxyHandler proxy. + RootedObject sandboxProxy(cx, getSandboxProxy(proxy)); + MOZ_ASSERT(js::IsProxy(sandboxProxy) && + js::GetProxyHandler(sandboxProxy) == &sandboxProxyHandler); + + // The global of the sandboxProxy is the sandbox global, and the + // target object is the original proto. + RootedObject sandboxGlobal(cx, JS::GetNonCCWObjectGlobal(sandboxProxy)); + MOZ_ASSERT(IsSandbox(sandboxGlobal)); + + // If our this object is the sandbox global, we call with this set to the + // original proto instead. + // + // There are two different ways we can compute |this|. If we use + // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the + // caller, which may be undefined if a global function was invoked without + // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this| + // in |vp| will be coerced to the global, which is not the correct + // behavior in ES5 strict mode. And we have no way to compute strictness + // here. + // + // The naive approach is simply to use JS_THIS_VALUE here. If |this| was + // explicit, we can remap it appropriately. If it was implicit, then we + // leave it as undefined, and let the callee sort it out. Since the callee + // is generally in the same compartment as its global (eg the Window's + // compartment, not the Sandbox's), the callee will generally compute the + // correct |this|. + // + // However, this breaks down in the Xray case. If the sandboxPrototype + // is an Xray wrapper, then we'll end up reifying the native methods in + // the Sandbox's scope, which means that they'll compute |this| to be the + // Sandbox, breaking old-style XPC_WN_CallMethod methods. + // + // Luckily, the intent of Xrays is to provide a vanilla view of a foreign + // DOM interface, which means that we don't care about script-enacted + // strictness in the prototype's home compartment. Indeed, since DOM + // methods are always non-strict, we can just assume non-strict semantics + // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately + // remap |this|. + bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy); + RootedValue thisVal(cx, args.thisv()); + if (isXray) { + RootedObject thisObject(cx); + if (!args.computeThis(cx, &thisObject)) { + return false; + } + thisVal.setObject(*thisObject); + } + + if (thisVal == ObjectValue(*sandboxGlobal)) { + thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); + } + + RootedValue func(cx, js::GetProxyPrivate(proxy)); + return JS::Call(cx, thisVal, func, args, args.rval()); +} + +/* + * Wrap a callable such that if we're called with oldThisObj as the + * "this" we will instead call it with newThisObj as the this. + */ +static JSObject* WrapCallable(JSContext* cx, HandleObject callable, + HandleObject sandboxProtoProxy) { + MOZ_ASSERT(JS::IsCallable(callable)); + // Our proxy is wrapping the callable. So we need to use the + // callable as the private. We put the given sandboxProtoProxy in + // an extra slot, and our call() hook depends on that. + MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && + js::GetProxyHandler(sandboxProtoProxy) == &sandboxProxyHandler); + + RootedValue priv(cx, ObjectValue(*callable)); + // We want to claim to have the same proto as our wrapped callable, so set + // ourselves up with a lazy proto. + js::ProxyOptions options; + options.setLazyProto(true); + JSObject* obj = js::NewProxyObject(cx, &sandboxCallableProxyHandler, priv, + nullptr, options); + if (obj) { + js::SetProxyReservedSlot(obj, SandboxCallableProxyHandler::SandboxProxySlot, + ObjectValue(*sandboxProtoProxy)); + } + + return obj; +} + +bool WrapAccessorFunction(JSContext* cx, MutableHandleObject accessor, + HandleObject sandboxProtoProxy) { + if (!accessor) { + return true; + } + + accessor.set(WrapCallable(cx, accessor, sandboxProtoProxy)); + return !!accessor; +} + +static bool IsMaybeWrappedDOMConstructor(JSObject* obj) { + // We really care about the underlying object here, which might be wrapped in + // cross-compartment wrappers. CheckedUnwrapStatic is fine, since we just + // care whether it's a DOM constructor. + obj = js::CheckedUnwrapStatic(obj); + if (!obj) { + return false; + } + + return dom::IsDOMConstructor(obj); +} + +bool SandboxProxyHandler::getPropertyDescriptorImpl( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + bool getOwn, MutableHandle<Maybe<PropertyDescriptor>> desc_) const { + JS::RootedObject obj(cx, wrappedObject(proxy)); + + MOZ_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(proxy)); + + if (getOwn) { + if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc_)) { + return false; + } + } else { + Rooted<JSObject*> holder(cx); + if (!JS_GetPropertyDescriptorById(cx, obj, id, desc_, &holder)) { + return false; + } + } + + if (desc_.isNothing()) { + return true; + } + + Rooted<PropertyDescriptor> desc(cx, *desc_); + + // Now fix up the getter/setter/value as needed. + if (desc.hasGetter() && !WrapAccessorFunction(cx, desc.getter(), proxy)) { + return false; + } + if (desc.hasSetter() && !WrapAccessorFunction(cx, desc.setter(), proxy)) { + return false; + } + if (desc.hasValue() && desc.value().isObject()) { + RootedObject val(cx, &desc.value().toObject()); + if (JS::IsCallable(val) && + // Don't wrap DOM constructors: they don't care about the "this" + // they're invoked with anyway, being constructors. And if we wrap + // them here we break invariants like Node == Node and whatnot. + !IsMaybeWrappedDOMConstructor(val)) { + val = WrapCallable(cx, val, proxy); + if (!val) { + return false; + } + desc.value().setObject(*val); + } + } + + desc_.set(Some(desc.get())); + return true; +} + +bool SandboxProxyHandler::getOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + MutableHandle<Maybe<PropertyDescriptor>> desc) const { + return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc); +} + +/* + * Reuse the BaseProxyHandler versions of the derived traps that are implemented + * in terms of the fundamental traps. + */ + +bool SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const { + // This uses JS_GetPropertyDescriptorById for backward compatibility. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { + return false; + } + + *bp = desc.isSome(); + return true; +} +bool SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool* bp) const { + return BaseProxyHandler::hasOwn(cx, proxy, id, bp); +} + +bool SandboxProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, + JS::Handle<jsid> id, + JS::MutableHandle<Value> vp) const { + // This uses JS_GetPropertyDescriptorById for backward compatibility. + Rooted<Maybe<PropertyDescriptor>> desc(cx); + if (!getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, &desc)) { + return false; + } + + if (desc.isNothing()) { + vp.setUndefined(); + return true; + } else { + desc->assertComplete(); + } + + // Everything after here follows [[Get]] for ordinary objects. + if (desc->isDataDescriptor()) { + vp.set(desc->value()); + return true; + } + + MOZ_ASSERT(desc->isAccessorDescriptor()); + RootedObject getter(cx, desc->getter()); + + if (!getter) { + vp.setUndefined(); + return true; + } + + return Call(cx, receiver, getter, HandleValueArray::empty(), vp); +} + +bool SandboxProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<Value> v, + JS::Handle<Value> receiver, + JS::ObjectOpResult& result) const { + return BaseProxyHandler::set(cx, proxy, id, v, receiver, result); +} + +bool SandboxProxyHandler::getOwnEnumerablePropertyKeys( + JSContext* cx, JS::Handle<JSObject*> proxy, + MutableHandleIdVector props) const { + return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); +} + +bool SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleIdVector props) const { + return BaseProxyHandler::enumerate(cx, proxy, props); +} + +bool xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) { + uint32_t length; + bool ok = JS::GetArrayLength(cx, obj, &length); + NS_ENSURE_TRUE(ok, false); + for (uint32_t i = 0; i < length; i++) { + RootedValue nameValue(cx); + ok = JS_GetElement(cx, obj, i, &nameValue); + NS_ENSURE_TRUE(ok, false); + if (!nameValue.isString()) { + JS_ReportErrorASCII(cx, "Property names must be strings"); + return false; + } + JSLinearString* nameStr = JS_EnsureLinearString(cx, nameValue.toString()); + if (!nameStr) { + return false; + } + + if (JS_LinearStringEqualsLiteral(nameStr, "AbortController")) { + AbortController = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Blob")) { + Blob = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "ChromeUtils")) { + ChromeUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "CSS")) { + CSS = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "CSSRule")) { + CSSRule = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Document")) { + Document = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Directory")) { + Directory = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMException")) { + DOMException = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMParser")) { + DOMParser = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "DOMTokenList")) { + DOMTokenList = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Element")) { + Element = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Event")) { + Event = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "File")) { + File = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "FileReader")) { + FileReader = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "FormData")) { + FormData = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Headers")) { + Headers = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "IOUtils")) { + IOUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "InspectorUtils")) { + InspectorUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MessageChannel")) { + MessageChannel = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIInputMap")) { + MIDIInputMap = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "MIDIOutputMap")) { + MIDIOutputMap = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Node")) { + Node = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "NodeFilter")) { + NodeFilter = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "PathUtils")) { + PathUtils = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Performance")) { + Performance = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "PromiseDebugging")) { + PromiseDebugging = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Range")) { + Range = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Selection")) { + Selection = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "TextDecoder")) { + TextDecoder = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "TextEncoder")) { + TextEncoder = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "URL")) { + URL = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "URLSearchParams")) { + URLSearchParams = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLHttpRequest")) { + XMLHttpRequest = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "WebSocket")) { + WebSocket = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "Window")) { + Window = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "XMLSerializer")) { + XMLSerializer = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "ReadableStream")) { + ReadableStream = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "atob")) { + atob = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "btoa")) { + btoa = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "caches")) { + caches = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "crypto")) { + crypto = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "fetch")) { + fetch = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "storage")) { + storage = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "structuredClone")) { + structuredClone = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "indexedDB")) { + indexedDB = true; + } else if (JS_LinearStringEqualsLiteral(nameStr, "isSecureContext")) { + isSecureContext = true; +#ifdef MOZ_WEBRTC + } else if (JS_LinearStringEqualsLiteral(nameStr, "rtcIdentityProvider")) { + rtcIdentityProvider = true; +#endif + } else { + RootedString nameStr(cx, nameValue.toString()); + JS::UniqueChars name = JS_EncodeStringToUTF8(cx, nameStr); + if (!name) { + return false; + } + + JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.get()); + return false; + } + } + return true; +} + +bool xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) { + MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj)); + // Properties will be exposed to System automatically but not to Sandboxes + // if |[Exposed=System]| is specified. + // This function holds common properties not exposed automatically but able + // to be requested either in |Cu.importGlobalProperties| or + // |wantGlobalProperties| of a sandbox. + if (AbortController && + !dom::AbortController_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Blob && !dom::Blob_Binding::GetConstructorObject(cx)) return false; + + if (ChromeUtils && !dom::ChromeUtils_Binding::GetConstructorObject(cx)) { + return false; + } + + if (CSS && !dom::CSS_Binding::GetConstructorObject(cx)) { + return false; + } + + if (CSSRule && !dom::CSSRule_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Directory && !dom::Directory_Binding::GetConstructorObject(cx)) + return false; + + if (Document && !dom::Document_Binding::GetConstructorObject(cx)) { + return false; + } + + if (DOMException && !dom::DOMException_Binding::GetConstructorObject(cx)) { + return false; + } + + if (DOMParser && !dom::DOMParser_Binding::GetConstructorObject(cx)) { + return false; + } + + if (DOMTokenList && !dom::DOMTokenList_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Element && !dom::Element_Binding::GetConstructorObject(cx)) return false; + + if (Event && !dom::Event_Binding::GetConstructorObject(cx)) return false; + + if (File && !dom::File_Binding::GetConstructorObject(cx)) return false; + + if (FileReader && !dom::FileReader_Binding::GetConstructorObject(cx)) { + return false; + } + + if (FormData && !dom::FormData_Binding::GetConstructorObject(cx)) + return false; + + if (Headers && !dom::Headers_Binding::GetConstructorObject(cx)) { + return false; + } + + if (IOUtils && !dom::IOUtils_Binding::GetConstructorObject(cx)) { + return false; + } + + if (InspectorUtils && !dom::InspectorUtils_Binding::GetConstructorObject(cx)) + return false; + + if (MessageChannel && + (!dom::MessageChannel_Binding::GetConstructorObject(cx) || + !dom::MessagePort_Binding::GetConstructorObject(cx))) + return false; + + if (MIDIInputMap && !dom::MIDIInputMap_Binding::GetConstructorObject(cx)) { + return false; + } + + if (MIDIOutputMap && !dom::MIDIOutputMap_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Node && !dom::Node_Binding::GetConstructorObject(cx)) { + return false; + } + + if (NodeFilter && !dom::NodeFilter_Binding::GetConstructorObject(cx)) { + return false; + } + + if (PathUtils && !dom::PathUtils_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Performance && !dom::Performance_Binding::GetConstructorObject(cx)) { + return false; + } + + if (PromiseDebugging && + !dom::PromiseDebugging_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Range && !dom::Range_Binding::GetConstructorObject(cx)) { + return false; + } + + if (Selection && !dom::Selection_Binding::GetConstructorObject(cx)) { + return false; + } + + if (TextDecoder && !dom::TextDecoder_Binding::GetConstructorObject(cx)) + return false; + + if (TextEncoder && !dom::TextEncoder_Binding::GetConstructorObject(cx)) + return false; + + if (URL && !dom::URL_Binding::GetConstructorObject(cx)) return false; + + if (URLSearchParams && + !dom::URLSearchParams_Binding::GetConstructorObject(cx)) + return false; + + if (XMLHttpRequest && !dom::XMLHttpRequest_Binding::GetConstructorObject(cx)) + return false; + + if (WebSocket && !dom::WebSocket_Binding::GetConstructorObject(cx)) + return false; + + if (Window && !dom::Window_Binding::GetConstructorObject(cx)) return false; + + if (XMLSerializer && !dom::XMLSerializer_Binding::GetConstructorObject(cx)) + return false; + + if (ReadableStream && !dom::ReadableStream_Binding::GetConstructorObject(cx)) + return false; + + if (atob && !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) return false; + + if (btoa && !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) return false; + + if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) { + return false; + } + + if (crypto && !SandboxCreateCrypto(cx, obj)) { + return false; + } + + if (fetch && !SandboxCreateFetch(cx, obj)) { + return false; + } + + if (storage && !SandboxCreateStorage(cx, obj)) { + return false; + } + + if (structuredClone && !SandboxCreateStructuredClone(cx, obj)) { + return false; + } + + // Note that isSecureContext here doesn't mean the context is actually secure + // - just that the caller wants the property defined + if (isSecureContext) { + bool hasSecureContext = IsSecureContextOrObjectIsFromSecureContext(cx, obj); + JS::Rooted<JS::Value> secureJsValue(cx, JS::BooleanValue(hasSecureContext)); + return JS_DefineProperty(cx, obj, "isSecureContext", secureJsValue, + JSPROP_ENUMERATE); + } + +#ifdef MOZ_WEBRTC + if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) { + return false; + } +#endif + + return true; +} + +bool xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx, + JS::HandleObject obj) { + if (indexedDB && !IndexedDatabaseManager::DefineIndexedDB(cx, obj)) + return false; + + return Define(cx, obj); +} + +bool xpc::GlobalProperties::DefineInSandbox(JSContext* cx, + JS::HandleObject obj) { + MOZ_ASSERT(IsSandbox(obj)); + MOZ_ASSERT(js::GetContextCompartment(cx) == JS::GetCompartment(obj)); + + if (indexedDB && !(IndexedDatabaseManager::ResolveSandboxBinding(cx) && + IndexedDatabaseManager::DefineIndexedDB(cx, obj))) + return false; + + return Define(cx, obj); +} + +/** + * If enabled, apply the extension base CSP, then apply the + * content script CSP which will either be a default or one + * provided by the extension in its manifest. + */ +nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) { + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop); + if (!principal) { + return NS_OK; + } + + auto* basePrin = BasePrincipal::Cast(principal); + // We only get an addonPolicy if the principal is an + // expanded principal with an extension principal in it. + auto* addonPolicy = basePrin->ContentScriptAddonPolicy(); + if (!addonPolicy) { + return NS_OK; + } + // For backwards compatibility, content scripts have no CSP + // in manifest v2. Only apply content script CSP to V3 or later. + if (addonPolicy->ManifestVersion() < 3) { + return NS_OK; + } + + nsString url; + MOZ_TRY_VAR(url, addonPolicy->GetURL(u""_ns)); + + nsCOMPtr<nsIURI> selfURI; + MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), url)); + + const nsAString& baseCSP = addonPolicy->BaseCSP(); + + // If we got here, we're definitly an expanded principal. + auto expanded = basePrin->As<ExpandedPrincipal>(); + nsCOMPtr<nsIContentSecurityPolicy> csp; + +#ifdef MOZ_DEBUG + // Bug 1548468: Move CSP off ExpandedPrincipal + expanded->GetCsp(getter_AddRefs(csp)); + if (csp) { + uint32_t count = 0; + csp->GetPolicyCount(&count); + if (count > 0) { + // Ensure that the policy was not already added. + nsAutoString parsedPolicyStr; + for (uint32_t i = 0; i < count; i++) { + csp->GetPolicyString(i, parsedPolicyStr); + MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP)); + } + } + } +#endif + + // Create a clone of the expanded principal to be used for the call to + // SetRequestContextWithPrincipal (to prevent the CSP and expanded + // principal instances to keep each other alive indefinitely, see + // Bug 1741600). + // + // This may not be necessary anymore once Bug 1548468 will move CSP + // off ExpandedPrincipal. + RefPtr<ExpandedPrincipal> clonedPrincipal = ExpandedPrincipal::Create( + expanded->AllowList(), expanded->OriginAttributesRef()); + MOZ_ASSERT(clonedPrincipal); + + csp = new nsCSPContext(); + MOZ_TRY( + csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, u""_ns, 0)); + + MOZ_TRY(csp->AppendPolicy(baseCSP, false, false)); + + expanded->SetCsp(csp); + return NS_OK; +} + +nsresult xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, + nsISupports* prinOrSop, + SandboxOptions& options) { + // Create the sandbox global object + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop); + nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(prinOrSop); + if (obj) { + nsGlobalWindowInner* window = + WindowOrNull(js::UncheckedUnwrap(obj->GetGlobalJSObject(), false)); + // If we have a secure context window inherit from it's parent + if (window && window->IsSecureContext()) { + options.forceSecureContext = true; + } + } + if (!principal) { + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } else { + RefPtr<NullPrincipal> nullPrin = + NullPrincipal::CreateWithoutOriginAttributes(); + principal = nullPrin; + } + } + MOZ_ASSERT(principal); + + JS::RealmOptions realmOptions; + + auto& creationOptions = realmOptions.creationOptions(); + + bool isSystemPrincipal = principal->IsSystemPrincipal(); + + if (isSystemPrincipal) { + options.forceSecureContext = true; + } + + // If we are able to see [SecureContext] API code + if (options.forceSecureContext) { + creationOptions.setSecureContext(true); + } + + xpc::SetPrefableRealmOptions(realmOptions); + if (options.sameZoneAs) { + creationOptions.setNewCompartmentInExistingZone( + js::UncheckedUnwrap(options.sameZoneAs)); + } else if (options.freshZone) { + creationOptions.setNewCompartmentAndZone(); + } else if (isSystemPrincipal && !options.invisibleToDebugger && + !options.freshCompartment) { + // Use a shared system compartment for system-principal sandboxes that don't + // require invisibleToDebugger (this is a compartment property, see bug + // 1482215). + creationOptions.setExistingCompartment(xpc::PrivilegedJunkScope()); + } else { + creationOptions.setNewCompartmentInSystemZone(); + } + + creationOptions.setInvisibleToDebugger(options.invisibleToDebugger) + .setTrace(TraceXPCGlobal); + + realmOptions.behaviors().setDiscardSource(options.discardSource); + + if (isSystemPrincipal) { + realmOptions.behaviors().setClampAndJitterTime(false); + } + + const JSClass* clasp = &SandboxClass; + + RootedObject sandbox( + cx, xpc::CreateGlobalObject(cx, clasp, principal, realmOptions)); + if (!sandbox) { + return NS_ERROR_FAILURE; + } + + // Use exclusive expandos for non-system-principal sandboxes. + bool hasExclusiveExpandos = !isSystemPrincipal; + + // Set up the wantXrays flag, which indicates whether xrays are desired even + // for same-origin access. + // + // This flag has historically been ignored for chrome sandboxes due to + // quirks in the wrapping implementation that have now been removed. Indeed, + // same-origin Xrays for chrome->chrome access seems a bit superfluous. + // Arguably we should just flip the default for chrome and still honor the + // flag, but such a change would break code in subtle ways for minimal + // benefit. So we just switch it off here. + bool wantXrays = AccessCheck::isChrome(sandbox) ? false : options.wantXrays; + + if (creationOptions.compartmentSpecifier() == + JS::CompartmentSpecifier::ExistingCompartment) { + // Make sure the compartment we're reusing has flags that match what we + // would set on a new compartment. + CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox); + MOZ_RELEASE_ASSERT(priv->allowWaivers == options.allowWaivers); + MOZ_RELEASE_ASSERT(priv->isWebExtensionContentScript == + options.isWebExtensionContentScript); + MOZ_RELEASE_ASSERT(priv->isUAWidgetCompartment == options.isUAWidgetScope); + MOZ_RELEASE_ASSERT(priv->hasExclusiveExpandos == hasExclusiveExpandos); + MOZ_RELEASE_ASSERT(priv->wantXrays == wantXrays); + } else { + CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox); + priv->allowWaivers = options.allowWaivers; + priv->isWebExtensionContentScript = options.isWebExtensionContentScript; + priv->isUAWidgetCompartment = options.isUAWidgetScope; + priv->hasExclusiveExpandos = hasExclusiveExpandos; + priv->wantXrays = wantXrays; + } + + { + JSAutoRealm ar(cx, sandbox); + + // This creates a SandboxPrivate and passes ownership of it to |sandbox|. + SandboxPrivate::Create(principal, sandbox); + + // Ensure |Object.prototype| is instantiated before prototype- + // splicing below. + if (!JS::GetRealmObjectPrototype(cx)) { + return NS_ERROR_XPC_UNEXPECTED; + } + + if (options.proto) { + bool ok = JS_WrapObject(cx, &options.proto); + if (!ok) { + return NS_ERROR_XPC_UNEXPECTED; + } + + // Now check what sort of thing we've got in |proto|, and figure out + // if we need a SandboxProxyHandler. + // + // Note that, in the case of a window, we can't require that the + // Sandbox subsumes the prototype, because we have to hold our + // reference to it via an outer window, and the window may navigate + // at any time. So we have to handle that case separately. + bool useSandboxProxy = + !!WindowOrNull(js::UncheckedUnwrap(options.proto, false)); + if (!useSandboxProxy) { + // We just wrapped options.proto into the compartment of whatever Realm + // is on the cx, so use that same realm for the CheckedUnwrapDynamic + // call. + JSObject* unwrappedProto = + js::CheckedUnwrapDynamic(options.proto, cx, false); + if (!unwrappedProto) { + JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype"); + return NS_ERROR_INVALID_ARG; + } + const JSClass* unwrappedClass = JS::GetClass(unwrappedProto); + useSandboxProxy = unwrappedClass->isWrappedNative() || + mozilla::dom::IsDOMClass(unwrappedClass); + } + + if (useSandboxProxy) { + // Wrap it up in a proxy that will do the right thing in terms + // of this-binding for methods. + RootedValue priv(cx, ObjectValue(*options.proto)); + options.proto = + js::NewProxyObject(cx, &sandboxProxyHandler, priv, nullptr); + if (!options.proto) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + ok = JS_SetPrototype(cx, sandbox, options.proto); + if (!ok) { + return NS_ERROR_XPC_UNEXPECTED; + } + } + + bool allowComponents = principal->IsSystemPrincipal(); + if (options.wantComponents && allowComponents) { + if (!ObjectScope(sandbox)->AttachComponentsObject(cx)) { + return NS_ERROR_XPC_UNEXPECTED; + } + + if (!ObjectScope(sandbox)->AttachJSServices(cx)) { + return NS_ERROR_XPC_UNEXPECTED; + } + } + + if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) { + return NS_ERROR_XPC_UNEXPECTED; + } + + if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) { + return NS_ERROR_XPC_UNEXPECTED; + } + + if (options.wantExportHelpers && + (!JS_DefineFunction(cx, sandbox, "exportFunction", + SandboxExportFunction, 3, 0) || + !JS_DefineFunction(cx, sandbox, "createObjectIn", + SandboxCreateObjectIn, 2, 0) || + !JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) || + !JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0))) + return NS_ERROR_XPC_UNEXPECTED; + + if (!options.globalProperties.DefineInSandbox(cx, sandbox)) { + return NS_ERROR_XPC_UNEXPECTED; + } + } + + // We handle the case where the context isn't in a compartment for the + // benefit of UnprivilegedJunkScope(). + vp.setObject(*sandbox); + if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp)) { + return NS_ERROR_UNEXPECTED; + } + + // Set the location information for the new global, so that tools like + // about:memory may use that information + xpc::SetLocationForGlobal(sandbox, options.sandboxName); + + xpc::SetSandboxMetadata(cx, sandbox, options.metadata); + + JSAutoRealm ar(cx, sandbox); + JS_FireOnNewGlobalObject(cx, sandbox); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +/* + * For sandbox constructor the first argument can be a URI string in which case + * we use the related Content Principal for the sandbox. + */ +bool ParsePrincipal(JSContext* cx, HandleString contentUrl, + const OriginAttributes& aAttrs, nsIPrincipal** principal) { + MOZ_ASSERT(principal); + MOZ_ASSERT(contentUrl); + nsCOMPtr<nsIURI> uri; + nsAutoJSString contentStr; + NS_ENSURE_TRUE(contentStr.init(cx, contentUrl), false); + nsresult rv = NS_NewURI(getter_AddRefs(uri), contentStr); + if (NS_FAILED(rv)) { + JS_ReportErrorASCII(cx, "Creating URI from string failed"); + return false; + } + + // We could allow passing in the app-id and browser-element info to the + // sandbox constructor. But creating a sandbox based on a string is a + // deprecated API so no need to add features to it. + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateContentPrincipal(uri, aAttrs); + prin.forget(principal); + + if (!*principal) { + JS_ReportErrorASCII(cx, "Creating Principal from URI failed"); + return false; + } + return true; +} + +/* + * For sandbox constructor the first argument can be a principal object or + * a script object principal (Document, Window). + */ +static bool GetPrincipalOrSOP(JSContext* cx, HandleObject from, + nsISupports** out) { + MOZ_ASSERT(out); + *out = nullptr; + + // We might have a Window here, so need ReflectorToISupportsDynamic + nsCOMPtr<nsISupports> native = ReflectorToISupportsDynamic(from, cx); + + if (nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(native)) { + sop.forget(out); + return true; + } + + nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(native); + principal.forget(out); + NS_ENSURE_TRUE(*out, false); + + return true; +} + +/* + * The first parameter of the sandbox constructor might be an array of + * principals, either in string format or actual objects (see GetPrincipalOrSOP) + */ +static bool GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj, + const SandboxOptions& options, + nsIExpandedPrincipal** out) { + MOZ_ASSERT(out); + uint32_t length; + + if (!JS::GetArrayLength(cx, arrayObj, &length)) { + return false; + } + if (!length) { + // We need a whitelist of principals or uri strings to create an + // expanded principal, if we got an empty array or something else + // report error. + JS_ReportErrorASCII(cx, "Expected an array of URI strings"); + return false; + } + + nsTArray<nsCOMPtr<nsIPrincipal>> allowedDomains(length); + allowedDomains.SetLength(length); + + // If an originAttributes option has been specified, we will use that as the + // OriginAttribute of all of the string arguments passed to this function. + // Otherwise, we will use the OriginAttributes of a principal or SOP object + // in the array, if any. If no such object is present, and all we have are + // strings, then we will use a default OriginAttribute. + // Otherwise, we will use the origin attributes of the passed object(s). If + // more than one object is specified, we ensure that the OAs match. + Maybe<OriginAttributes> attrs; + if (options.originAttributes) { + attrs.emplace(); + JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); + if (!attrs->Init(cx, val)) { + // The originAttributes option, if specified, must be valid! + JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); + return false; + } + } + + // Now we go over the array in two passes. In the first pass, we ignore + // strings, and only process objects. Assuming that no originAttributes + // option has been passed, if we encounter a principal or SOP object, we + // grab its OA and save it if it's the first OA encountered, otherwise + // check to make sure that it is the same as the OA found before. + // In the second pass, we ignore objects, and use the OA found in pass 0 + // (or the previously computed OA if we have obtained it from the options) + // to construct content principals. + // + // The effective OA selected above will also be set as the OA of the + // expanded principal object. + + // First pass: + for (uint32_t i = 0; i < length; ++i) { + RootedValue allowed(cx); + if (!JS_GetElement(cx, arrayObj, i, &allowed)) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal; + if (allowed.isObject()) { + // In case of object let's see if it's a Principal or a + // ScriptObjectPrincipal. + nsCOMPtr<nsISupports> prinOrSop; + RootedObject obj(cx, &allowed.toObject()); + if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) { + return false; + } + + nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(prinOrSop)); + principal = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } + NS_ENSURE_TRUE(principal, false); + + if (!options.originAttributes) { + const OriginAttributes prinAttrs = principal->OriginAttributesRef(); + if (attrs.isNothing()) { + attrs.emplace(prinAttrs); + } else if (prinAttrs != attrs.ref()) { + // If attrs is from a previously encountered principal in the + // array, we need to ensure that it matches the OA of the + // principal we have here. + // If attrs comes from OriginAttributes, we don't need + // this check. + return false; + } + } + + // We do not allow ExpandedPrincipals to contain any system principals. + bool isSystem = principal->IsSystemPrincipal(); + if (isSystem) { + JS_ReportErrorASCII( + cx, "System principal is not allowed in an expanded principal"); + return false; + } + allowedDomains[i] = principal; + } else if (allowed.isString()) { + // Skip any string arguments - we handle them in the next pass. + } else { + // Don't know what this is. + return false; + } + } + + if (attrs.isNothing()) { + // If no OriginAttributes was found in the first pass, fall back to a + // default one. + attrs.emplace(); + } + + // Second pass: + for (uint32_t i = 0; i < length; ++i) { + RootedValue allowed(cx); + if (!JS_GetElement(cx, arrayObj, i, &allowed)) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal; + if (allowed.isString()) { + // In case of string let's try to fetch a content principal from it. + RootedString str(cx, allowed.toString()); + + // attrs here is either a default OriginAttributes in case the + // originAttributes option isn't specified, and no object in the array + // provides a principal. Otherwise it's either the forced principal, or + // the principal found before, so we can use it here. + if (!ParsePrincipal(cx, str, attrs.ref(), getter_AddRefs(principal))) { + return false; + } + NS_ENSURE_TRUE(principal, false); + allowedDomains[i] = principal; + } else { + MOZ_ASSERT(allowed.isObject()); + } + } + + RefPtr<ExpandedPrincipal> result = + ExpandedPrincipal::Create(allowedDomains, attrs.ref()); + result.forget(out); + return true; +} + +/* + * Helper that tries to get a property from the options object. + */ +bool OptionsBase::ParseValue(const char* name, MutableHandleValue prop, + bool* aFound) { + bool found; + bool ok = JS_HasProperty(mCx, mObject, name, &found); + NS_ENSURE_TRUE(ok, false); + + if (aFound) { + *aFound = found; + } + + if (!found) { + return true; + } + + return JS_GetProperty(mCx, mObject, name, prop); +} + +/* + * Helper that tries to get a boolean property from the options object. + */ +bool OptionsBase::ParseBoolean(const char* name, bool* prop) { + MOZ_ASSERT(prop); + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!value.isBoolean()) { + JS_ReportErrorASCII(mCx, "Expected a boolean value for property %s", name); + return false; + } + + *prop = value.toBoolean(); + return true; +} + +/* + * Helper that tries to get an object property from the options object. + */ +bool OptionsBase::ParseObject(const char* name, MutableHandleObject prop) { + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!value.isObject()) { + JS_ReportErrorASCII(mCx, "Expected an object value for property %s", name); + return false; + } + prop.set(&value.toObject()); + return true; +} + +/* + * Helper that tries to get an object property from the options object. + */ +bool OptionsBase::ParseJSString(const char* name, MutableHandleString prop) { + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + prop.set(value.toString()); + return true; +} + +/* + * Helper that tries to get a string property from the options object. + */ +bool OptionsBase::ParseString(const char* name, nsCString& prop) { + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + + JS::UniqueChars tmp = JS_EncodeStringToLatin1(mCx, value.toString()); + NS_ENSURE_TRUE(tmp, false); + prop.Assign(tmp.get(), strlen(tmp.get())); + return true; +} + +/* + * Helper that tries to get a string property from the options object. + */ +bool OptionsBase::ParseString(const char* name, nsString& prop) { + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + + nsAutoJSString strVal; + if (!strVal.init(mCx, value.toString())) { + return false; + } + + prop = strVal; + return true; +} + +/* + * Helper that tries to get jsid property from the options object. + */ +bool OptionsBase::ParseId(const char* name, MutableHandleId prop) { + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + return JS_ValueToId(mCx, value, prop); +} + +/* + * Helper that tries to get a uint32_t property from the options object. + */ +bool OptionsBase::ParseUInt32(const char* name, uint32_t* prop) { + MOZ_ASSERT(prop); + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) { + return true; + } + + if (!JS::ToUint32(mCx, value, prop)) { + JS_ReportErrorASCII(mCx, "Expected a uint32_t value for property %s", name); + return false; + } + + return true; +} + +/* + * Helper that tries to get a list of DOM constructors and other helpers from + * the options object. + */ +bool SandboxOptions::ParseGlobalProperties() { + RootedValue value(mCx); + bool found; + bool ok = ParseValue("wantGlobalProperties", &value, &found); + NS_ENSURE_TRUE(ok, false); + if (!found) { + return true; + } + + if (!value.isObject()) { + JS_ReportErrorASCII(mCx, + "Expected an array value for wantGlobalProperties"); + return false; + } + + RootedObject ctors(mCx, &value.toObject()); + bool isArray; + if (!JS::IsArrayObject(mCx, ctors, &isArray)) { + return false; + } + if (!isArray) { + JS_ReportErrorASCII(mCx, + "Expected an array value for wantGlobalProperties"); + return false; + } + + return globalProperties.Parse(mCx, ctors); +} + +/* + * Helper that parsing the sandbox options object (from) and sets the fields of + * the incoming options struct (options). + */ +bool SandboxOptions::Parse() { + /* All option names must be ASCII-only. */ + bool ok = ParseObject("sandboxPrototype", &proto) && + ParseBoolean("wantXrays", &wantXrays) && + ParseBoolean("allowWaivers", &allowWaivers) && + ParseBoolean("wantComponents", &wantComponents) && + ParseBoolean("wantExportHelpers", &wantExportHelpers) && + ParseBoolean("isWebExtensionContentScript", + &isWebExtensionContentScript) && + ParseBoolean("forceSecureContext", &forceSecureContext) && + ParseString("sandboxName", sandboxName) && + ParseObject("sameZoneAs", &sameZoneAs) && + ParseBoolean("freshCompartment", &freshCompartment) && + ParseBoolean("freshZone", &freshZone) && + ParseBoolean("invisibleToDebugger", &invisibleToDebugger) && + ParseBoolean("discardSource", &discardSource) && + ParseGlobalProperties() && ParseValue("metadata", &metadata) && + ParseUInt32("userContextId", &userContextId) && + ParseObject("originAttributes", &originAttributes); + if (!ok) { + return false; + } + + if (freshZone && sameZoneAs) { + JS_ReportErrorASCII(mCx, "Cannot use both sameZoneAs and freshZone"); + return false; + } + + return true; +} + +static nsresult AssembleSandboxMemoryReporterName(JSContext* cx, + nsCString& sandboxName) { + // Use a default name when the caller did not provide a sandboxName. + if (sandboxName.IsEmpty()) { + sandboxName = "[anonymous sandbox]"_ns; + } else { +#ifndef DEBUG + // Adding the caller location is fairly expensive, so in non-debug + // builds, only add it if we don't have an explicit sandbox name. + return NS_OK; +#endif + } + + // Get the xpconnect native call context. + XPCCallContext* cc = XPCJSContext::Get()->GetCallContext(); + NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); + + // Get the current source info from xpc. + nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(); + + // Append the caller's location information. + if (frame) { + nsString location; + frame->GetFilename(cx, location); + int32_t lineNumber = frame->GetLineNumber(cx); + + sandboxName.AppendLiteral(" (from: "); + sandboxName.Append(NS_ConvertUTF16toUTF8(location)); + sandboxName.Append(':'); + sandboxName.AppendInt(lineNumber); + sandboxName.Append(')'); + } + + return NS_OK; +} + +// static +nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct( + nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) { + if (args.length() < 1) { + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + } + + nsresult rv; + bool ok = false; + bool calledWithOptions = args.length() > 1; + if (calledWithOptions && !args[1].isObject()) { + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + RootedObject optionsObject(cx, + calledWithOptions ? &args[1].toObject() : nullptr); + + SandboxOptions options(cx, optionsObject); + if (calledWithOptions && !options.Parse()) { + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + // Make sure to set up principals on the sandbox before initing classes. + nsCOMPtr<nsIPrincipal> principal; + nsCOMPtr<nsIExpandedPrincipal> expanded; + nsCOMPtr<nsISupports> prinOrSop; + + if (args[0].isString()) { + RootedString str(cx, args[0].toString()); + OriginAttributes attrs; + if (options.originAttributes) { + JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); + if (!attrs.Init(cx, val)) { + // The originAttributes option, if specified, must be valid! + JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + } + attrs.mUserContextId = options.userContextId; + ok = ParsePrincipal(cx, str, attrs, getter_AddRefs(principal)); + prinOrSop = principal; + } else if (args[0].isObject()) { + RootedObject obj(cx, &args[0].toObject()); + bool isArray; + if (!JS::IsArrayObject(cx, obj, &isArray)) { + ok = false; + } else if (isArray) { + if (options.userContextId != 0) { + // We don't support passing a userContextId with an array. + ok = false; + } else { + ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded)); + prinOrSop = expanded; + // If this is an addon content script we need to apply the csp. + MOZ_TRY(ApplyAddonContentScriptCSP(prinOrSop)); + } + } else { + ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); + } + } else if (args[0].isNull()) { + // Null means that we just pass prinOrSop = nullptr, and get an + // NullPrincipal. + ok = true; + } + + if (!ok) { + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) { + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + + if (options.metadata.isNullOrUndefined()) { + // If the caller is running in a sandbox, inherit. + RootedObject callerGlobal(cx, JS::GetScriptedCallerGlobal(cx)); + if (IsSandbox(callerGlobal)) { + rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options); + + if (NS_FAILED(rv)) { + return ThrowAndFail(rv, cx, _retval); + } + + *_retval = true; + return NS_OK; +} + +nsresult xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg, + const nsAString& source, const nsACString& filename, + int32_t lineNo, bool enforceFilenameRestrictions, + MutableHandleValue rval) { + JS_AbortIfWrongThread(cx); + rval.set(UndefinedValue()); + + bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); + // CheckedUnwrapStatic is fine here, since we're checking for "is it a + // sandbox". + RootedObject sandbox(cx, js::CheckedUnwrapStatic(sandboxArg)); + if (!sandbox || !IsSandbox(sandbox)) { + return NS_ERROR_INVALID_ARG; + } + + SandboxPrivate* priv = SandboxPrivate::GetPrivate(sandbox); + nsIScriptObjectPrincipal* sop = priv; + MOZ_ASSERT(sop, "Invalid sandbox passed"); + nsCOMPtr<nsIPrincipal> prin = sop->GetPrincipal(); + NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); + + nsAutoCString filenameBuf; + if (!filename.IsVoid() && filename.Length() != 0) { + filenameBuf.Assign(filename); + } else { + // Default to the spec of the principal. + nsresult rv = nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); + NS_ENSURE_SUCCESS(rv, rv); + lineNo = 1; + } + + // We create a separate cx to do the sandbox evaluation. Scope it. + RootedValue v(cx, UndefinedValue()); + RootedValue exn(cx, UndefinedValue()); + bool ok = true; + { + // We're about to evaluate script, so make an AutoEntryScript. + // This is clearly Gecko-specific and not in any spec. + mozilla::dom::AutoEntryScript aes(priv, "XPConnect sandbox evaluation"); + JSContext* sandcx = aes.cx(); + JSAutoRealm ar(sandcx, sandbox); + + JS::CompileOptions options(sandcx); + options.setFileAndLine(filenameBuf.get(), lineNo); + options.setSkipFilenameValidation(!enforceFilenameRestrictions); + MOZ_ASSERT(JS_IsGlobalObject(sandbox)); + + const nsPromiseFlatString& flat = PromiseFlatString(source); + + JS::SourceText<char16_t> buffer; + ok = buffer.init(sandcx, flat.get(), flat.Length(), + JS::SourceOwnership::Borrowed) && + JS::Evaluate(sandcx, options, buffer, &v); + + // If the sandbox threw an exception, grab it off the context. + if (aes.HasException()) { + if (!aes.StealException(&exn)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + // + // Alright, we're back on the caller's cx. If an error occured, try to + // wrap and set the exception. Otherwise, wrap the return value. + // + + if (!ok) { + // If we end up without an exception, it was probably due to OOM along + // the way, in which case we thow. Otherwise, wrap it. + if (exn.isUndefined() || !JS_WrapValue(cx, &exn)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Set the exception on our caller's cx. + JS_SetPendingException(cx, exn); + return NS_ERROR_FAILURE; + } + + // Transitively apply Xray waivers if |sb| was waived. + if (waiveXray) { + ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); + } else { + ok = JS_WrapValue(cx, &v); + } + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + // Whew! + rval.set(v); + return NS_OK; +} + +nsresult xpc::GetSandboxMetadata(JSContext* cx, HandleObject sandbox, + MutableHandleValue rval) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsSandbox(sandbox)); + + RootedValue metadata(cx); + { + JSAutoRealm ar(cx, sandbox); + metadata = + JS::GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT); + } + + if (!JS_WrapValue(cx, &metadata)) { + return NS_ERROR_UNEXPECTED; + } + + rval.set(metadata); + return NS_OK; +} + +nsresult xpc::SetSandboxMetadata(JSContext* cx, HandleObject sandbox, + HandleValue metadataArg) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsSandbox(sandbox)); + + RootedValue metadata(cx); + + JSAutoRealm ar(cx, sandbox); + if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr)) { + return NS_ERROR_UNEXPECTED; + } + + JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata); + + return NS_OK; +} + +ModuleLoaderBase* SandboxPrivate::GetModuleLoader(JSContext* aCx) { + if (mModuleLoader) { + return mModuleLoader; + } + + JSObject* object = GetGlobalJSObject(); + nsGlobalWindowInner* sandboxWindow = xpc::SandboxWindowOrNull(object, aCx); + if (!sandboxWindow) { + return nullptr; + } + + ModuleLoader* mainModuleLoader = + static_cast<ModuleLoader*>(sandboxWindow->GetModuleLoader(aCx)); + + ScriptLoader* scriptLoader = mainModuleLoader->GetScriptLoader(); + + ModuleLoader* moduleLoader = + new ModuleLoader(scriptLoader, this, ModuleLoader::WebExtension); + scriptLoader->RegisterContentScriptModuleLoader(moduleLoader); + mModuleLoader = moduleLoader; + + return moduleLoader; +} + +mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> +SandboxPrivate::GetStorageKey() { + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::ipc::PrincipalInfo principalInfo; + nsresult rv = PrincipalToPrincipalInfo(mPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return mozilla::Err(rv); + } + + // Block expanded and null principals, let content and system through. + if (principalInfo.type() != + mozilla::ipc::PrincipalInfo::TContentPrincipalInfo && + principalInfo.type() != + mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) { + return Err(NS_ERROR_DOM_SECURITY_ERR); + } + + return std::move(principalInfo); +} diff --git a/js/xpconnect/src/SandboxPrivate.h b/js/xpconnect/src/SandboxPrivate.h new file mode 100644 index 0000000000..345893864d --- /dev/null +++ b/js/xpconnect/src/SandboxPrivate.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +#ifndef __SANDBOXPRIVATE_H__ +#define __SANDBOXPRIVATE_H__ + +#include "mozilla/WeakPtr.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/net/CookieJarSettings.h" +#include "nsContentUtils.h" +#include "nsIGlobalObject.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIPrincipal.h" +#include "nsGlobalWindowInner.h" +#include "nsWeakReference.h" +#include "nsWrapperCache.h" + +#include "js/loader/ModuleLoaderBase.h" + +#include "js/Object.h" // JS::GetPrivate, JS::SetPrivate +#include "js/RootingAPI.h" + +class SandboxPrivate : public nsIGlobalObject, + public nsIScriptObjectPrincipal, + public nsSupportsWeakReference, + public mozilla::SupportsWeakPtr, + public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(SandboxPrivate, + nsIGlobalObject) + + static void Create(nsIPrincipal* principal, JS::Handle<JSObject*> global) { + RefPtr<SandboxPrivate> sbp = new SandboxPrivate(principal); + sbp->SetWrapper(global); + sbp->PreserveWrapper(ToSupports(sbp.get())); + + // Pass on ownership of sbp to |global|. + // The type used to cast to void needs to match the one in GetPrivate. + nsIScriptObjectPrincipal* sop = + static_cast<nsIScriptObjectPrincipal*>(sbp.forget().take()); + JS::SetObjectISupports(global, sop); + } + + static SandboxPrivate* GetPrivate(JSObject* obj) { + // The type used to cast to void needs to match the one in Create. + nsIScriptObjectPrincipal* sop = + JS::GetObjectISupports<nsIScriptObjectPrincipal>(obj); + return static_cast<SandboxPrivate*>(sop); + } + + mozilla::OriginTrials Trials() const final { return {}; } + + nsIPrincipal* GetPrincipal() override { return mPrincipal; } + + nsIPrincipal* GetEffectiveCookiePrincipal() override { return mPrincipal; } + + nsIPrincipal* GetEffectiveStoragePrincipal() override { return mPrincipal; } + + nsIPrincipal* PartitionedPrincipal() override { return mPrincipal; } + + JSObject* GetGlobalJSObject() override { return GetWrapper(); } + JSObject* GetGlobalJSObjectPreserveColor() const override { + return GetWrapperPreserveColor(); + } + + mozilla::StorageAccess GetStorageAccess() final { + MOZ_ASSERT(NS_IsMainThread()); + if (mozilla::StaticPrefs::dom_serviceWorkers_testing_enabled()) { + // XXX: This is a hack to workaround bug 1732159 and is not intended + return mozilla::StorageAccess::eAllow; + } + nsCOMPtr<nsICookieJarSettings> cookieJarSettings = + mozilla::net::CookieJarSettings::Create(mPrincipal); + return mozilla::StorageAllowedForServiceWorker(mPrincipal, + cookieJarSettings); + } + + void ForgetGlobalObject(JSObject* obj) { ClearWrapper(obj); } + + virtual JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override { + MOZ_CRASH("SandboxPrivate doesn't use DOM bindings!"); + } + + JS::loader::ModuleLoaderBase* GetModuleLoader(JSContext* aCx) override; + + mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> GetStorageKey() + override; + + size_t ObjectMoved(JSObject* obj, JSObject* old) { + UpdateWrapper(obj, old); + return 0; + } + + bool ShouldResistFingerprinting( + RFPTarget aTarget = RFPTarget::Unknown) const override { + return nsContentUtils::ShouldResistFingerprinting( + "Presently we don't have enough context to make an informed decision" + "on JS Sandboxes. See 1782853", + aTarget); + } + + private: + explicit SandboxPrivate(nsIPrincipal* principal) : mPrincipal(principal) {} + + virtual ~SandboxPrivate() = default; + + nsCOMPtr<nsIPrincipal> mPrincipal; + + RefPtr<JS::loader::ModuleLoaderBase> mModuleLoader; +}; + +#endif // __SANDBOXPRIVATE_H__ diff --git a/js/xpconnect/src/XPCCallContext.cpp b/js/xpconnect/src/XPCCallContext.cpp new file mode 100644 index 0000000000..b7da79ba9a --- /dev/null +++ b/js/xpconnect/src/XPCCallContext.cpp @@ -0,0 +1,218 @@ +/* -*- 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/. */ + +/* Call context. */ + +#include "xpcprivate.h" +#include "jsfriendapi.h" +#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot +#include "js/Wrapper.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace xpc; +using namespace JS; + +static inline bool IsTearoffClass(const JSClass* clazz) { + return clazz == &XPC_WN_Tearoff_JSClass; +} + +XPCCallContext::XPCCallContext( + JSContext* cx, HandleObject obj /* = nullptr */, + HandleObject funobj /* = nullptr */, + HandleId name /* = JSID_VOID */, unsigned argc /* = NO_ARGS */, + Value* argv /* = nullptr */, Value* rval /* = nullptr */) + : mState(INIT_FAILED), + mXPC(nsXPConnect::XPConnect()), + mXPCJSContext(nullptr), + mJSContext(cx), + mWrapper(nullptr), + mTearOff(nullptr), + mMember(nullptr), + mName(cx), + mStaticMemberIsLocal(false), + mArgc(0), + mArgv(nullptr), + mRetVal(nullptr) { + MOZ_ASSERT(cx); + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + + if (!mXPC) { + return; + } + + mXPCJSContext = XPCJSContext::Get(); + + // hook into call context chain. + mPrevCallContext = mXPCJSContext->SetCallContext(this); + + mState = HAVE_CONTEXT; + + if (!obj) { + return; + } + + mMethodIndex = 0xDEAD; + + mState = HAVE_OBJECT; + + mTearOff = nullptr; + + JSObject* unwrapped = + js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false); + if (!unwrapped) { + JS_ReportErrorASCII(mJSContext, + "Permission denied to call method on |this|"); + mState = INIT_FAILED; + return; + } + const JSClass* clasp = JS::GetClass(unwrapped); + if (clasp->isWrappedNative()) { + mWrapper = XPCWrappedNative::Get(unwrapped); + } else if (IsTearoffClass(clasp)) { + mTearOff = XPCWrappedNativeTearOff::Get(unwrapped); + mWrapper = XPCWrappedNative::Get( + &JS::GetReservedSlot(unwrapped, XPCWrappedNativeTearOff::FlatObjectSlot) + .toObject()); + } + if (mWrapper && !mTearOff) { + mScriptable = mWrapper->GetScriptable(); + } + + if (!name.isVoid()) { + SetName(name); + } + + if (argc != NO_ARGS) { + SetArgsAndResultPtr(argc, argv, rval); + } + + CHECK_STATE(HAVE_OBJECT); +} + +void XPCCallContext::SetName(jsid name) { + CHECK_STATE(HAVE_OBJECT); + + mName = name; + + if (mTearOff) { + mSet = nullptr; + mInterface = mTearOff->GetInterface(); + mMember = mInterface->FindMember(mName); + mStaticMemberIsLocal = true; + if (mMember && !mMember->IsConstant()) { + mMethodIndex = mMember->GetIndex(); + } + } else { + mSet = mWrapper ? mWrapper->GetSet() : nullptr; + + if (mSet && + mSet->FindMember( + mName, &mMember, &mInterface, + mWrapper->HasProto() ? mWrapper->GetProto()->GetSet() : nullptr, + &mStaticMemberIsLocal)) { + if (mMember && !mMember->IsConstant()) { + mMethodIndex = mMember->GetIndex(); + } + } else { + mMember = nullptr; + mInterface = nullptr; + mStaticMemberIsLocal = false; + } + } + + mState = HAVE_NAME; +} + +void XPCCallContext::SetCallInfo(XPCNativeInterface* iface, + XPCNativeMember* member, bool isSetter) { + CHECK_STATE(HAVE_CONTEXT); + + // We are going straight to the method info and need not do a lookup + // by id. + + // don't be tricked if method is called with wrong 'this' + if (mTearOff && mTearOff->GetInterface() != iface) { + mTearOff = nullptr; + } + + mSet = nullptr; + mInterface = iface; + mMember = member; + mMethodIndex = mMember->GetIndex() + (isSetter ? 1 : 0); + mName = mMember->GetName(); + + if (mState < HAVE_NAME) { + mState = HAVE_NAME; + } +} + +void XPCCallContext::SetArgsAndResultPtr(unsigned argc, Value* argv, + Value* rval) { + CHECK_STATE(HAVE_OBJECT); + + if (mState < HAVE_NAME) { + mSet = nullptr; + mInterface = nullptr; + mMember = nullptr; + mStaticMemberIsLocal = false; + } + + mArgc = argc; + mArgv = argv; + mRetVal = rval; + + mState = HAVE_ARGS; +} + +nsresult XPCCallContext::CanCallNow() { + nsresult rv; + + if (!HasInterfaceAndMember()) { + return NS_ERROR_UNEXPECTED; + } + if (mState < HAVE_ARGS) { + return NS_ERROR_UNEXPECTED; + } + + if (!mTearOff) { + mTearOff = mWrapper->FindTearOff(mJSContext, mInterface, false, &rv); + if (!mTearOff || mTearOff->GetInterface() != mInterface) { + mTearOff = nullptr; + return NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED; + } + } + + // Refresh in case FindTearOff extended the set + mSet = mWrapper->GetSet(); + + mState = READY_TO_CALL; + return NS_OK; +} + +void XPCCallContext::SystemIsBeingShutDown() { + // XXX This is pretty questionable since the per thread cleanup stuff + // can be making this call on one thread for call contexts on another + // thread. + NS_WARNING( + "Shutting Down XPConnect even through there is a live XPCCallContext"); + mXPCJSContext = nullptr; + mState = SYSTEM_SHUTDOWN; + mSet = nullptr; + mInterface = nullptr; + + if (mPrevCallContext) { + mPrevCallContext->SystemIsBeingShutDown(); + } +} + +XPCCallContext::~XPCCallContext() { + if (mXPCJSContext) { + DebugOnly<XPCCallContext*> old = + mXPCJSContext->SetCallContext(mPrevCallContext); + MOZ_ASSERT(old == this, "bad pop from per thread data"); + } +} diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp new file mode 100644 index 0000000000..77df85b5f8 --- /dev/null +++ b/js/xpconnect/src/XPCComponents.cpp @@ -0,0 +1,2665 @@ +/* -*- 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/. */ + +/* The "Components" xpcom objects for JavaScript. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "JSServices.h" +#include "XPCJSWeakReference.h" +#include "AccessCheck.h" +#include "WrapperFactory.h" +#include "nsJSUtils.h" +#include "mozJSModuleLoader.h" +#include "nsContentUtils.h" +#include "nsCycleCollector.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::IsArrayObject +#include "js/CallAndConstruct.h" // JS::IsCallable, JS_CallFunctionName, JS_CallFunctionValue +#include "js/CharacterEncoding.h" +#include "js/ContextOptions.h" +#include "js/friend/WindowProxy.h" // js::ToWindowProxyIfWindow +#include "js/Object.h" // JS::GetClass, JS::GetCompartment +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty, JS_SetPropertyById +#include "js/SavedFrameAPI.h" +#include "js/StructuredClone.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Attributes.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Preferences.h" +#include "nsJSEnvironment.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/URLPreloader.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/RemoteObjectProxy.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/WindowBinding.h" +#include "nsZipArchive.h" +#include "nsWindowMemoryReporter.h" +#include "nsICycleCollectorListener.h" +#include "nsIException.h" +#include "nsIScriptError.h" +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "nsScriptError.h" +#include "GeckoProfiler.h" +#include "ProfilerControl.h" +#include "mozilla/EditorSpellCheck.h" +#include "nsCommandLine.h" +#include "nsCommandParams.h" +#include "nsPersistentProperties.h" +#include "nsIDocumentEncoder.h" + +using namespace mozilla; +using namespace JS; +using namespace js; +using namespace xpc; +using mozilla::dom::Exception; + +/***************************************************************************/ +// stuff used by all + +nsresult xpc::ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval) { + XPCThrower::Throw(errNum, cx); + *retval = false; + return NS_OK; +} + +static bool JSValIsInterfaceOfType(JSContext* cx, HandleValue v, REFNSIID iid) { + nsCOMPtr<nsIXPConnectWrappedNative> wn; + nsCOMPtr<nsISupports> iface; + + if (v.isPrimitive()) { + return false; + } + + nsIXPConnect* xpc = nsIXPConnect::XPConnect(); + RootedObject obj(cx, &v.toObject()); + return NS_SUCCEEDED( + xpc->GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wn))) && + wn && + NS_SUCCEEDED( + wn->Native()->QueryInterface(iid, getter_AddRefs(iface))) && + iface; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class nsXPCComponents_Interfaces final : public nsIXPCComponents_Interfaces, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_INTERFACES + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_Interfaces(); + + private: + virtual ~nsXPCComponents_Interfaces(); +}; + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Interfaces), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_Interfaces"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Interfaces::nsXPCComponents_Interfaces() = default; + +nsXPCComponents_Interfaces::~nsXPCComponents_Interfaces() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_Interfaces, nsIXPCComponents_Interfaces, + nsIXPCScriptable, nsIClassInfo); + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Interfaces +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Interfaces" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Interfaces::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::MutableHandleIdVector properties, + bool enumerableOnly, bool* _retval) { + if (!properties.reserve(nsXPTInterfaceInfo::InterfaceCount())) { + *_retval = false; + return NS_OK; + } + + for (uint32_t index = 0; index < nsXPTInterfaceInfo::InterfaceCount(); + index++) { + const nsXPTInterfaceInfo* interface = nsXPTInterfaceInfo::ByIndex(index); + if (!interface) { + continue; + } + + const char* name = interface->Name(); + if (!name) { + continue; + } + + RootedString idstr(cx, JS_NewStringCopyZ(cx, name)); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + properties.infallibleAppend(id); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, jsid idArg, + bool* resolvedp, bool* _retval) { + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + + if (!id.isString()) { + return NS_OK; + } + + RootedString str(cx, id.toString()); + JS::UniqueChars name = JS_EncodeStringToLatin1(cx, str); + + // we only allow interfaces by name here + if (name && name[0] != '{') { + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByName(name.get()); + if (!info) { + return NS_OK; + } + + RootedValue iidv(cx); + if (xpc::IfaceID2JSValue(cx, *info, &iidv)) { + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, iidv, + JSPROP_ENUMERATE | JSPROP_READONLY | + JSPROP_PERMANENT | JSPROP_RESOLVING); + } + } + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class nsXPCComponents_Classes final : public nsIXPCComponents_Classes, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_CLASSES + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_Classes(); + + private: + virtual ~nsXPCComponents_Classes(); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Classes::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Classes), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_Classes"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Classes::nsXPCComponents_Classes() = default; + +nsXPCComponents_Classes::~nsXPCComponents_Classes() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_Classes, nsIXPCComponents_Classes, + nsIXPCScriptable, nsIClassInfo) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Classes +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Classes" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Classes::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::MutableHandleIdVector properties, + bool enumerableOnly, bool* _retval) { + nsCOMPtr<nsIComponentRegistrar> compMgr; + if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || + !compMgr) { + return NS_ERROR_UNEXPECTED; + } + + nsTArray<nsCString> contractIDs; + if (NS_FAILED(compMgr->GetContractIDs(contractIDs))) { + return NS_ERROR_UNEXPECTED; + } + + for (const auto& name : contractIDs) { + RootedString idstr(cx, JS_NewStringCopyN(cx, name.get(), name.Length())); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + if (!properties.append(id)) { + *_retval = false; + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, jsid idArg, + bool* resolvedp, bool* _retval) + +{ + RootedId id(cx, idArg); + RootedObject obj(cx, objArg); + + RootedValue cidv(cx); + if (id.isString() && xpc::ContractID2JSValue(cx, id.toString(), &cidv)) { + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, cidv, + JSPROP_ENUMERATE | JSPROP_READONLY | + JSPROP_PERMANENT | JSPROP_RESOLVING); + } + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +// Currently the possible results do not change at runtime, so they are only +// cached once (unlike ContractIDs, CLSIDs, and IIDs) + +class nsXPCComponents_Results final : public nsIXPCComponents_Results, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_RESULTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_Results(); + + private: + virtual ~nsXPCComponents_Results(); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Results::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Results), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_Results"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Results::nsXPCComponents_Results() = default; + +nsXPCComponents_Results::~nsXPCComponents_Results() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_Results, nsIXPCComponents_Results, + nsIXPCScriptable, nsIClassInfo) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Results +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Results" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Results::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::MutableHandleIdVector properties, + bool enumerableOnly, bool* _retval) { + const char* name; + const void* iter = nullptr; + while (nsXPCException::IterateNSResults(nullptr, &name, nullptr, &iter)) { + RootedString idstr(cx, JS_NewStringCopyZ(cx, name)); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + if (!properties.append(id)) { + *_retval = false; + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, jsid idArg, + bool* resolvedp, bool* _retval) { + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + if (!id.isString()) { + return NS_OK; + } + + JS::UniqueChars name = JS_EncodeStringToLatin1(cx, id.toString()); + if (name) { + const char* rv_name; + const void* iter = nullptr; + nsresult rv; + while (nsXPCException::IterateNSResults(&rv, &rv_name, nullptr, &iter)) { + if (!strcmp(name.get(), rv_name)) { + *resolvedp = true; + if (!JS_DefinePropertyById(cx, obj, id, (uint32_t)rv, + JSPROP_ENUMERATE | JSPROP_READONLY | + JSPROP_PERMANENT | JSPROP_RESOLVING)) { + return NS_ERROR_UNEXPECTED; + } + } + } + } + return NS_OK; +} + +/***************************************************************************/ +// JavaScript Constructor for nsIJSID objects (Components.ID) + +class nsXPCComponents_ID final : public nsIXPCComponents_ID, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_ID + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_ID(); + + private: + virtual ~nsXPCComponents_ID(); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_ID::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_ID), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_ID"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_ID::nsXPCComponents_ID() = default; + +nsXPCComponents_ID::~nsXPCComponents_ID() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_ID, nsIXPCComponents_ID, nsIXPCScriptable, + nsIClassInfo) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_ID +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ID" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \ + XPC_SCRIPTABLE_WANT_HASINSTANCE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_ID::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, + bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_ID::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, + bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// static +nsresult nsXPCComponents_ID::CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, + bool* _retval) { + // make sure we have at least one arg + + if (args.length() < 1) { + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + } + + // Prevent non-chrome code from creating ID objects. + if (!nsContentUtils::IsCallerChrome()) { + return ThrowAndFail(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, cx, _retval); + } + + // convert the first argument into a string and see if it looks like an id + + JSString* jsstr = ToString(cx, args[0]); + if (!jsstr) { + return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval); + } + + JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, jsstr); + if (!bytes) { + return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval); + } + + nsID id; + if (!id.Parse(bytes.get())) { + return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval); + } + + // make the new object and return it. + + if (!xpc::ID2JSValue(cx, id, args.rval())) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, HandleValue val, + bool* bp, bool* _retval) { + if (bp) { + *bp = xpc::JSValue2ID(cx, val).isSome(); + } + return NS_OK; +} + +/***************************************************************************/ +// JavaScript Constructor for Exception objects (Components.Exception) + +class nsXPCComponents_Exception final : public nsIXPCComponents_Exception, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_EXCEPTION + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_Exception(); + + private: + virtual ~nsXPCComponents_Exception(); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Exception::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Exception), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_Exception"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Exception::nsXPCComponents_Exception() = default; + +nsXPCComponents_Exception::~nsXPCComponents_Exception() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_Exception, nsIXPCComponents_Exception, + nsIXPCScriptable, nsIClassInfo) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Exception +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Exception" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \ + XPC_SCRIPTABLE_WANT_HASINSTANCE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Exception::Call(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_Exception::Construct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +struct MOZ_STACK_CLASS ExceptionArgParser { + ExceptionArgParser(JSContext* context, nsIXPConnect* xpconnect) + : eMsg("exception"), + eResult(NS_ERROR_FAILURE), + cx(context), + xpc(xpconnect) {} + + // Public exception parameter values. During construction, these are + // initialized to the appropriate defaults. + const char* eMsg; + nsresult eResult; + nsCOMPtr<nsIStackFrame> eStack; + nsCOMPtr<nsISupports> eData; + + // Parse the constructor arguments into the above |eFoo| parameter values. + bool parse(const CallArgs& args) { + /* + * The Components.Exception takes a series of arguments, all of them + * optional: + * + * Argument 0: Exception message (defaults to 'exception'). + * Argument 1: Result code (defaults to NS_ERROR_FAILURE) _or_ options + * object (see below). + * Argument 2: Stack (defaults to the current stack, which we trigger + * by leaving this nullptr in the parser). + * Argument 3: Optional user data (defaults to nullptr). + * + * To dig our way out of this clunky API, we now support passing an + * options object as the second parameter (as opposed to a result code). + * If this is the case, all subsequent arguments are ignored, and the + * following properties are parsed out of the object (using the + * associated default if the property does not exist): + * + * result: Result code (see argument 1). + * stack: Call stack (see argument 2). + * data: User data (see argument 3). + */ + if (args.length() > 0 && !parseMessage(args[0])) { + return false; + } + if (args.length() > 1) { + if (args[1].isObject()) { + RootedObject obj(cx, &args[1].toObject()); + return parseOptionsObject(obj); + } + if (!parseResult(args[1])) { + return false; + } + } + if (args.length() > 2) { + if (!parseStack(args[2])) { + return false; + } + } + if (args.length() > 3) { + if (!parseData(args[3])) { + return false; + } + } + return true; + } + + protected: + /* + * Parsing helpers. + */ + + bool parseMessage(HandleValue v) { + JSString* str = ToString(cx, v); + if (!str) { + return false; + } + messageBytes = JS_EncodeStringToLatin1(cx, str); + eMsg = messageBytes.get(); + return !!eMsg; + } + + bool parseResult(HandleValue v) { + return JS::ToUint32(cx, v, (uint32_t*)&eResult); + } + + bool parseStack(HandleValue v) { + if (!v.isObject()) { + // eStack has already been initialized to null, which is what we want + // for any non-object values (including null). + return true; + } + + RootedObject stackObj(cx, &v.toObject()); + return NS_SUCCEEDED(xpc->WrapJS(cx, stackObj, NS_GET_IID(nsIStackFrame), + getter_AddRefs(eStack))); + } + + bool parseData(HandleValue v) { + if (!v.isObject()) { + // eData has already been initialized to null, which is what we want + // for any non-object values (including null). + return true; + } + + RootedObject obj(cx, &v.toObject()); + return NS_SUCCEEDED( + xpc->WrapJS(cx, obj, NS_GET_IID(nsISupports), getter_AddRefs(eData))); + } + + bool parseOptionsObject(HandleObject obj) { + RootedValue v(cx); + + if (!getOption(obj, "result", &v) || (!v.isUndefined() && !parseResult(v))) + return false; + + if (!getOption(obj, "stack", &v) || (!v.isUndefined() && !parseStack(v))) + return false; + + if (!getOption(obj, "data", &v) || (!v.isUndefined() && !parseData(v))) + return false; + + return true; + } + + bool getOption(HandleObject obj, const char* name, MutableHandleValue rv) { + // Look for the property. + bool found; + if (!JS_HasProperty(cx, obj, name, &found)) { + return false; + } + + // If it wasn't found, indicate with undefined. + if (!found) { + rv.setUndefined(); + return true; + } + + // Get the property. + return JS_GetProperty(cx, obj, name, rv); + } + + /* + * Internal data members. + */ + + // If there's a non-default exception string, hold onto the allocated bytes. + JS::UniqueChars messageBytes; + + // Various bits and pieces that are helpful to have around. + JSContext* cx; + nsIXPConnect* xpc; +}; + +// static +nsresult nsXPCComponents_Exception::CallOrConstruct( + nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) { + nsIXPConnect* xpc = nsIXPConnect::XPConnect(); + + MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsCallerChrome()); + + // Parse the arguments to the Exception constructor. + ExceptionArgParser parser(cx, xpc); + if (!parser.parse(args)) { + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + } + + RefPtr<Exception> e = new Exception(nsCString(parser.eMsg), parser.eResult, + ""_ns, parser.eStack, parser.eData); + + RootedObject newObj(cx); + if (NS_FAILED(xpc->WrapNative(cx, obj, e, NS_GET_IID(nsIException), + newObj.address())) || + !newObj) { + return ThrowAndFail(NS_ERROR_XPC_CANT_CREATE_WN, cx, _retval); + } + + args.rval().setObject(*newObj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + HandleValue val, bool* bp, + bool* _retval) { + using namespace mozilla::dom; + + if (bp) { + *bp = (val.isObject() && IS_INSTANCE_OF(Exception, &val.toObject())) || + JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIException)); + } + return NS_OK; +} + +/*******************************************************/ +// JavaScript Constructor for nsIXPCConstructor objects (Components.Constructor) + +class nsXPCComponents_Constructor final : public nsIXPCComponents_Constructor, + public nsIXPCScriptable, + public nsIClassInfo { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_CONSTRUCTOR + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + public: + nsXPCComponents_Constructor(); + + private: + virtual ~nsXPCComponents_Constructor(); + static bool InnerConstructor(JSContext* cx, unsigned argc, JS::Value* vp); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Constructor::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCComponents_Constructor), + NS_GET_IID(nsIXPCScriptable)}; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetScriptableHelper(nsIXPCScriptable** retval) { + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassDescription( + nsACString& aClassDescription) { + aClassDescription.AssignLiteral("XPCComponents_Constructor"); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Constructor::nsXPCComponents_Constructor() = default; + +nsXPCComponents_Constructor::~nsXPCComponents_Constructor() { + // empty +} + +NS_IMPL_ISUPPORTS(nsXPCComponents_Constructor, nsIXPCComponents_Constructor, + nsIXPCScriptable, nsIClassInfo) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Constructor +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Constructor" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_CALL | XPC_SCRIPTABLE_WANT_CONSTRUCT | \ + XPC_SCRIPTABLE_WANT_HASINSTANCE | \ + XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE) +#include "xpc_map_end.h" /* This will #undef the above */ + +// static +bool nsXPCComponents_Constructor::InnerConstructor(JSContext* cx, unsigned argc, + JS::Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + + // Fetch the property name ids, so we can look them up. + XPCJSRuntime* runtime = XPCJSRuntime::Get(); + HandleId classIDProp = runtime->GetStringID(XPCJSContext::IDX_CLASS_ID); + HandleId interfaceIDProp = + runtime->GetStringID(XPCJSContext::IDX_INTERFACE_ID); + HandleId initializerProp = + runtime->GetStringID(XPCJSContext::IDX_INITIALIZER); + + // Get properties ('classID', 'interfaceID', and 'initializer') off the + // constructor object. + RootedValue classIDv(cx); + RootedValue interfaceID(cx); + RootedValue initializer(cx); + if (!JS_GetPropertyById(cx, callee, classIDProp, &classIDv) || + !JS_GetPropertyById(cx, callee, interfaceIDProp, &interfaceID) || + !JS_GetPropertyById(cx, callee, initializerProp, &initializer)) { + return false; + } + if (!classIDv.isObject() || !interfaceID.isObject()) { + XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); + return false; + } + + // Call 'createInstance' on the 'classID' object to create the object. + RootedValue instancev(cx); + RootedObject classID(cx, &classIDv.toObject()); + if (!JS_CallFunctionName(cx, classID, "createInstance", + HandleValueArray(interfaceID), &instancev)) { + return false; + } + if (!instancev.isObject()) { + XPCThrower::Throw(NS_ERROR_FAILURE, cx); + return false; + } + + // Call the method 'initializer' on the instance, passing in our parameters. + if (!initializer.isUndefined()) { + RootedValue dummy(cx); + RootedValue initfunc(cx); + RootedId initid(cx); + RootedObject instance(cx, &instancev.toObject()); + if (!JS_ValueToId(cx, initializer, &initid) || + !JS_GetPropertyById(cx, instance, initid, &initfunc) || + !JS_CallFunctionValue(cx, instance, initfunc, args, &dummy)) { + return false; + } + } + + args.rval().set(instancev); + return true; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::Call(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::Construct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) { + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// static +nsresult nsXPCComponents_Constructor::CallOrConstruct( + nsIXPConnectWrappedNative* wrapper, JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) { + // make sure we have at least one arg + + if (args.length() < 1) { + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + } + + // Fetch the property name ids, so we can look them up. + XPCJSRuntime* runtime = XPCJSRuntime::Get(); + HandleId classIDProp = runtime->GetStringID(XPCJSContext::IDX_CLASS_ID); + HandleId interfaceIDProp = + runtime->GetStringID(XPCJSContext::IDX_INTERFACE_ID); + HandleId initializerProp = + runtime->GetStringID(XPCJSContext::IDX_INITIALIZER); + + // get the various other object pointers we need + + nsIXPConnect* xpc = nsIXPConnect::XPConnect(); + XPCWrappedNativeScope* scope = ObjectScope(obj); + nsCOMPtr<nsIXPCComponents> comp; + + if (!xpc || !scope || !(comp = scope->GetComponents())) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + // Prevent non-chrome code from creating constructor objects. + if (!nsContentUtils::IsCallerChrome()) { + return ThrowAndFail(NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED, cx, _retval); + } + + JSFunction* ctorfn = JS_NewFunction(cx, InnerConstructor, 0, + JSFUN_CONSTRUCTOR, "XPCOM_Constructor"); + if (!ctorfn) { + return ThrowAndFail(NS_ERROR_OUT_OF_MEMORY, cx, _retval); + } + + JS::RootedObject ctor(cx, JS_GetFunctionObject(ctorfn)); + + if (args.length() >= 3) { + // args[2] is an initializer function or property name + RootedString str(cx, ToString(cx, args[2])); + if (!JS_DefinePropertyById( + cx, ctor, initializerProp, str, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { + return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval); + } + } + + RootedString ifaceName(cx); + if (args.length() >= 2) { + ifaceName = ToString(cx, args[1]); + } else { + ifaceName = JS_NewStringCopyZ(cx, "nsISupports"); + } + + if (!ifaceName) { + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + } + + // a new scope to avoid warnings about shadowed names + { + nsCOMPtr<nsIXPCComponents_Interfaces> ifaces; + RootedObject ifacesObj(cx); + + // we do the lookup by asking the Components.interfaces object + // for the property with this name - i.e. we let its caching of these + // nsIJSIID objects work for us. + + if (NS_FAILED(comp->GetInterfaces(getter_AddRefs(ifaces))) || + NS_FAILED(xpc->WrapNative(cx, obj, ifaces, + NS_GET_IID(nsIXPCComponents_Interfaces), + ifacesObj.address())) || + !ifacesObj) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + RootedId id(cx); + if (!JS_StringToId(cx, ifaceName, &id)) { + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + } + + RootedValue val(cx); + if (!JS_GetPropertyById(cx, ifacesObj, id, &val) || val.isPrimitive()) { + return ThrowAndFail(NS_ERROR_XPC_BAD_IID, cx, _retval); + } + + if (!JS_DefinePropertyById( + cx, ctor, interfaceIDProp, val, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { + return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval); + } + } + + // a new scope to avoid warnings about shadowed names + { + // argv[0] is a contractid name string + + // we do the lookup by asking the Components.classes object + // for the property with this name - i.e. we let its caching of these + // nsIJSCID objects work for us. + + nsCOMPtr<nsIXPCComponents_Classes> classes; + RootedObject classesObj(cx); + + if (NS_FAILED(comp->GetClasses(getter_AddRefs(classes))) || + NS_FAILED(xpc->WrapNative(cx, obj, classes, + NS_GET_IID(nsIXPCComponents_Classes), + classesObj.address())) || + !classesObj) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + RootedString str(cx, ToString(cx, args[0])); + RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id)) { + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + } + + RootedValue val(cx); + if (!JS_GetPropertyById(cx, classesObj, id, &val) || val.isPrimitive()) { + return ThrowAndFail(NS_ERROR_XPC_BAD_CID, cx, _retval); + } + + if (!JS_DefinePropertyById( + cx, ctor, classIDProp, val, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { + return ThrowAndFail(NS_ERROR_FAILURE, cx, _retval); + } + } + + args.rval().setObject(*ctor); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + HandleValue val, bool* isa, + bool* _retval) { + *isa = + val.isObject() && JS_IsNativeFunction(&val.toObject(), InnerConstructor); + return NS_OK; +} + +class nsXPCComponents_Utils final : public nsIXPCComponents_Utils, + public nsIXPCScriptable { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSIXPCCOMPONENTS_UTILS + + public: + nsXPCComponents_Utils() = default; + + private: + virtual ~nsXPCComponents_Utils() = default; + nsCOMPtr<nsIXPCComponents_utils_Sandbox> mSandbox; +}; + +NS_IMPL_ISUPPORTS(nsXPCComponents_Utils, nsIXPCComponents_Utils, + nsIXPCScriptable) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Utils +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Utils" +#define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Utils::GetSandbox(nsIXPCComponents_utils_Sandbox** aSandbox) { + NS_ENSURE_ARG_POINTER(aSandbox); + if (!mSandbox) { + mSandbox = NewSandboxConstructor(); + } + + nsCOMPtr<nsIXPCComponents_utils_Sandbox> rval = mSandbox; + rval.forget(aSandbox); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateServicesCache(JSContext* aCx, + MutableHandleValue aServices) { + if (JSObject* services = NewJSServices(aCx)) { + aServices.setObject(*services); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::PrintStderr(const nsACString& message) { + printf_stderr("%s", PromiseFlatUTF8String(message).get()); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ReportError(HandleValue error, HandleValue stack, + JSContext* cx) { + // This function shall never fail! Silently eat any failure conditions. + + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!console) { + return NS_OK; + } + + nsGlobalWindowInner* win = CurrentWindowOrNull(cx); + const uint64_t innerWindowID = win ? win->WindowID() : 0; + + Rooted<Maybe<Value>> exception(cx, Some(error)); + if (!innerWindowID) { + // Leak mitigation: nsConsoleService::ClearMessagesForWindowID needs + // a WindowID for cleanup and exception values could hold arbitrary + // objects alive. + exception = Nothing(); + } + + nsCOMPtr<nsIScriptError> scripterr; + RootedObject errorObj(cx, error.isObject() ? &error.toObject() : nullptr); + if (errorObj) { + JS::RootedObject stackVal(cx); + JS::RootedObject stackGlobal(cx); + FindExceptionStackForConsoleReport(win, error, nullptr, &stackVal, + &stackGlobal); + if (stackVal) { + scripterr = CreateScriptError(win, exception, stackVal, stackGlobal); + } + } + + nsString fileName; + uint32_t lineNo = 0; + + if (!scripterr) { + RootedObject stackObj(cx); + RootedObject stackGlobal(cx); + if (stack.isObject()) { + if (!JS::IsMaybeWrappedSavedFrame(&stack.toObject())) { + return NS_ERROR_INVALID_ARG; + } + + // |stack| might be a wrapper, but it must be same-compartment with + // the current global. + stackObj = &stack.toObject(); + stackGlobal = JS::CurrentGlobalOrNull(cx); + js::AssertSameCompartment(stackObj, stackGlobal); + + JSPrincipals* principals = + JS::GetRealmPrincipals(js::GetContextRealm(cx)); + + if (GetSavedFrameLine(cx, principals, stackObj, &lineNo) != + SavedFrameResult::Ok) { + JS_ClearPendingException(cx); + } + + RootedString source(cx); + nsAutoJSString str; + if (GetSavedFrameSource(cx, principals, stackObj, &source) == + SavedFrameResult::Ok && + str.init(cx, source)) { + fileName = str; + } else { + JS_ClearPendingException(cx); + } + } else { + nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(); + if (frame) { + frame->GetFilename(cx, fileName); + lineNo = frame->GetLineNumber(cx); + JS::Rooted<JS::Value> stack(cx); + nsresult rv = frame->GetNativeSavedFrame(&stack); + if (NS_SUCCEEDED(rv) && stack.isObject()) { + stackObj = &stack.toObject(); + MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj)); + stackGlobal = JS::GetNonCCWObjectGlobal(stackObj); + } + } + } + + if (stackObj) { + scripterr = CreateScriptError(win, exception, stackObj, stackGlobal); + } + } + + if (!scripterr) { + scripterr = CreateScriptError(win, exception, nullptr, nullptr); + } + + JSErrorReport* err = errorObj ? JS_ErrorFromException(cx, errorObj) : nullptr; + if (err) { + // It's a proper JS Error + nsAutoString fileUni; + CopyUTF8toUTF16(mozilla::MakeStringSpan(err->filename), fileUni); + + uint32_t column = err->tokenOffset(); + + const char16_t* linebuf = err->linebuf(); + uint32_t flags = err->isWarning() ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag; + + nsresult rv = scripterr->InitWithWindowID( + err->message() ? NS_ConvertUTF8toUTF16(err->message().c_str()) + : EmptyString(), + fileUni, + linebuf ? nsDependentString(linebuf, err->linebufLength()) + : EmptyString(), + err->lineno, column, flags, "XPConnect JavaScript", innerWindowID, + innerWindowID == 0 ? true : false); + NS_ENSURE_SUCCESS(rv, NS_OK); + + console->LogMessage(scripterr); + return NS_OK; + } + + // It's not a JS Error object, so we synthesize as best we're able. + RootedString msgstr(cx, ToString(cx, error)); + if (!msgstr) { + return NS_OK; + } + + nsAutoJSString msg; + if (!msg.init(cx, msgstr)) { + return NS_OK; + } + + nsresult rv = scripterr->InitWithWindowID( + msg, fileName, u""_ns, lineNo, 0, 0, "XPConnect JavaScript", + innerWindowID, innerWindowID == 0 ? true : false); + NS_ENSURE_SUCCESS(rv, NS_OK); + + console->LogMessage(scripterr); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::EvalInSandbox( + const nsAString& source, HandleValue sandboxVal, HandleValue version, + const nsACString& filenameArg, int32_t lineNumber, + bool enforceFilenameRestrictions, JSContext* cx, uint8_t optionalArgc, + MutableHandleValue retval) { + RootedObject sandbox(cx); + if (!JS_ValueToObject(cx, sandboxVal, &sandbox) || !sandbox) { + return NS_ERROR_INVALID_ARG; + } + + // Optional third argument: JS version, as a string, is unused. + + // Optional fourth and fifth arguments: filename and line number. + int32_t lineNo = (optionalArgc >= 3) ? lineNumber : 1; + nsCString filename; + if (!filenameArg.IsVoid()) { + filename.Assign(filenameArg); + } else { + // Get the current source info. + nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(); + if (frame) { + nsString frameFile; + frame->GetFilename(cx, frameFile); + CopyUTF16toUTF8(frameFile, filename); + lineNo = frame->GetLineNumber(cx); + } + } + enforceFilenameRestrictions = + (optionalArgc >= 4) ? enforceFilenameRestrictions : true; + + return xpc::EvalInSandbox(cx, sandbox, source, filename, lineNo, + enforceFilenameRestrictions, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetUAWidgetScope(nsIPrincipal* principal, JSContext* cx, + MutableHandleValue rval) { + rval.set(UndefinedValue()); + + JSObject* scope = xpc::GetUAWidgetScope(cx, principal); + + rval.set(JS::ObjectValue(*scope)); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetSandboxMetadata(HandleValue sandboxVal, JSContext* cx, + MutableHandleValue rval) { + if (!sandboxVal.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + RootedObject sandbox(cx, &sandboxVal.toObject()); + // We only care about sandboxes here, so CheckedUnwrapStatic is fine. + sandbox = js::CheckedUnwrapStatic(sandbox); + if (!sandbox || !xpc::IsSandbox(sandbox)) { + return NS_ERROR_INVALID_ARG; + } + + return xpc::GetSandboxMetadata(cx, sandbox, rval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetSandboxMetadata(HandleValue sandboxVal, + HandleValue metadataVal, + JSContext* cx) { + if (!sandboxVal.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + RootedObject sandbox(cx, &sandboxVal.toObject()); + // We only care about sandboxes here, so CheckedUnwrapStatic is fine. + sandbox = js::CheckedUnwrapStatic(sandbox); + if (!sandbox || !xpc::IsSandbox(sandbox)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = xpc::SetSandboxMetadata(cx, sandbox, metadataVal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Import(const nsACString& registryLocation, + HandleValue targetObj, JSContext* cx, + uint8_t optionalArgc, MutableHandleValue retval) { + RefPtr moduleloader = mozJSModuleLoader::Get(); + MOZ_ASSERT(moduleloader); + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("nsXPCComponents_Utils::Import", OTHER, + registryLocation); + + return moduleloader->ImportInto(registryLocation, targetObj, cx, optionalArgc, + retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsModuleLoaded(const nsACString& aResourceURI, + bool* retval) { + RefPtr moduleloader = mozJSModuleLoader::Get(); + MOZ_ASSERT(moduleloader); + return moduleloader->IsModuleLoaded(aResourceURI, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsJSModuleLoaded(const nsACString& aResourceURI, + bool* retval) { + RefPtr moduleloader = mozJSModuleLoader::Get(); + MOZ_ASSERT(moduleloader); + return moduleloader->IsJSModuleLoaded(aResourceURI, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsESModuleLoaded(const nsACString& aResourceURI, + bool* retval) { + RefPtr moduleloader = mozJSModuleLoader::Get(); + MOZ_ASSERT(moduleloader); + return moduleloader->IsESModuleLoaded(aResourceURI, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Unload(const nsACString& registryLocation) { + RefPtr moduleloader = mozJSModuleLoader::Get(); + MOZ_ASSERT(moduleloader); + return moduleloader->Unload(registryLocation); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ImportGlobalProperties(HandleValue aPropertyList, + JSContext* cx) { + // Ensure we're working in the scripted caller's realm. This is not guaranteed + // to be the current realm because we switch realms when calling cross-realm + // functions. + RootedObject global(cx, JS::GetScriptedCallerGlobal(cx)); + MOZ_ASSERT(global); + js::AssertSameCompartment(cx, global); + JSAutoRealm ar(cx, global); + + // Don't allow doing this if the global is a Window. + nsGlobalWindowInner* win; + if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, win))) { + return NS_ERROR_NOT_AVAILABLE; + } + + GlobalProperties options; + NS_ENSURE_TRUE(aPropertyList.isObject(), NS_ERROR_INVALID_ARG); + + RootedObject propertyList(cx, &aPropertyList.toObject()); + bool isArray; + if (NS_WARN_IF(!JS::IsArrayObject(cx, propertyList, &isArray))) { + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!isArray)) { + return NS_ERROR_INVALID_ARG; + } + + if (!options.Parse(cx, propertyList) || + !options.DefineInXPCComponents(cx, global)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWeakReference(HandleValue object, JSContext* cx, + xpcIJSWeakReference** _retval) { + RefPtr<xpcJSWeakReference> ref = new xpcJSWeakReference(); + nsresult rv = ref->Init(cx, object); + NS_ENSURE_SUCCESS(rv, rv); + ref.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceGC(JSContext* aCx) { + PrepareForFullGC(aCx); + NonIncrementalGC(aCx, GCOptions::Normal, GCReason::COMPONENT_UTILS); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceCC(nsICycleCollectorListener* listener) { + nsJSContext::CycleCollectNow(CCReason::API, listener); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateCCLogger(nsICycleCollectorListener** aListener) { + NS_ENSURE_ARG_POINTER(aListener); + nsCOMPtr<nsICycleCollectorListener> logger = nsCycleCollector_createLogger(); + logger.forget(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::FinishCC() { + nsCycleCollector_finishAnyCurrentCollection(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CcSlice(int64_t budget) { + nsJSContext::RunCycleCollectorWorkSlice(budget); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetMaxCCSliceTimeSinceClear(int32_t* out) { + *out = nsJSContext::GetMaxCCSliceTimeSinceClear(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ClearMaxCCTime() { + nsJSContext::ClearMaxCCSliceTime(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceShrinkingGC(JSContext* aCx) { + PrepareForFullGC(aCx); + NonIncrementalGC(aCx, GCOptions::Shrink, GCReason::COMPONENT_UTILS); + return NS_OK; +} + +class PreciseGCRunnable : public Runnable { + public: + PreciseGCRunnable(nsIScheduledGCCallback* aCallback, bool aShrinking) + : mozilla::Runnable("PreciseGCRunnable"), + mCallback(aCallback), + mShrinking(aShrinking) {} + + NS_IMETHOD Run() override { + nsJSContext::GarbageCollectNow( + GCReason::COMPONENT_UTILS, + mShrinking ? nsJSContext::ShrinkingGC : nsJSContext::NonShrinkingGC); + + mCallback->Callback(); + return NS_OK; + } + + private: + nsCOMPtr<nsIScheduledGCCallback> mCallback; + bool mShrinking; +}; + +NS_IMETHODIMP +nsXPCComponents_Utils::SchedulePreciseGC(nsIScheduledGCCallback* aCallback) { + RefPtr<PreciseGCRunnable> event = new PreciseGCRunnable(aCallback, false); + return NS_DispatchToMainThread(event); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SchedulePreciseShrinkingGC( + nsIScheduledGCCallback* aCallback) { + RefPtr<PreciseGCRunnable> event = new PreciseGCRunnable(aCallback, true); + return NS_DispatchToMainThread(event); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnlinkGhostWindows() { +#ifdef DEBUG + nsWindowMemoryReporter::UnlinkGhostWindows(); + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService(); + if (obsvc) { + obsvc->NotifyObservers(nullptr, "child-ghost-request", nullptr); + } + } + + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef NS_FREE_PERMANENT_DATA +struct IntentionallyLeakedObject { + MOZ_COUNTED_DEFAULT_CTOR(IntentionallyLeakedObject) + + MOZ_COUNTED_DTOR(IntentionallyLeakedObject) +}; +#endif + +NS_IMETHODIMP +nsXPCComponents_Utils::IntentionallyLeak() { +#ifdef NS_FREE_PERMANENT_DATA + Unused << new IntentionallyLeakedObject(); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetJSTestingFunctions(JSContext* cx, + MutableHandleValue retval) { + JSObject* obj = js::GetTestingFunctions(cx); + if (!obj) { + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + } + retval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetFunctionSourceLocation(HandleValue funcValue, + JSContext* cx, + MutableHandleValue retval) { + NS_ENSURE_TRUE(funcValue.isObject(), NS_ERROR_INVALID_ARG); + + nsAutoString filename; + uint32_t lineNumber; + { + RootedObject funcObj(cx, UncheckedUnwrap(&funcValue.toObject())); + JSAutoRealm ar(cx, funcObj); + + Rooted<JSFunction*> func(cx, JS_GetObjectFunction(funcObj)); + NS_ENSURE_TRUE(func, NS_ERROR_INVALID_ARG); + + RootedScript script(cx, JS_GetFunctionScript(cx, func)); + NS_ENSURE_TRUE(func, NS_ERROR_FAILURE); + + AppendUTF8toUTF16(nsDependentCString(JS_GetScriptFilename(script)), + filename); + lineNumber = JS_GetScriptBaseLineNumber(cx, script) + 1; + } + + RootedObject res(cx, JS_NewPlainObject(cx)); + NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY); + + RootedValue filenameVal(cx); + if (!xpc::NonVoidStringToJsval(cx, filename, &filenameVal) || + !JS_DefineProperty(cx, res, "filename", filenameVal, JSPROP_ENUMERATE)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!JS_DefineProperty(cx, res, "lineNumber", lineNumber, JSPROP_ENUMERATE)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + retval.setObject(*res); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function, + nsIStackFrame* stack, + const nsAString& asyncCause, + JSContext* cx, + MutableHandleValue retval) { + nsresult rv; + + if (!stack || asyncCause.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JS::Value> asyncStack(cx); + rv = stack->GetNativeSavedFrame(&asyncStack); + if (NS_FAILED(rv)) { + return rv; + } + if (!asyncStack.isObject()) { + JS_ReportErrorASCII(cx, "Must use a native JavaScript stack frame"); + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject()); + + NS_ConvertUTF16toUTF8 utf8Cause(asyncCause); + JS::AutoSetAsyncStackForNewCalls sas( + cx, asyncStackObj, utf8Cause.get(), + JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT); + + if (!JS_CallFunctionValue(cx, nullptr, function, + JS::HandleValueArray::empty(), retval)) { + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetGlobalForObject(HandleValue object, JSContext* cx, + MutableHandleValue retval) { + // First argument must be an object. + if (object.isPrimitive()) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + // When getting the global for a cross-compartment wrapper, we really want + // a wrapper for the foreign global. So we need to unwrap before getting the + // global and then wrap the result. + Rooted<JSObject*> obj(cx, &object.toObject()); + obj = JS::GetNonCCWObjectGlobal(js::UncheckedUnwrap(obj)); + + if (!JS_WrapObject(cx, &obj)) { + return NS_ERROR_FAILURE; + } + + // Get the WindowProxy if necessary. + obj = js::ToWindowProxyIfWindow(obj); + + retval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsProxy(HandleValue vobj, JSContext* cx, bool* rval) { + if (!vobj.isObject()) { + *rval = false; + return NS_OK; + } + + RootedObject obj(cx, &vobj.toObject()); + // We need to do a dynamic unwrap, because we apparently want to treat + // "failure to unwrap" differently from "not a proxy" (throw for the former, + // return false for the latter). + obj = js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false); + NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE); + + *rval = js::IsScriptedProxy(obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ExportFunction(HandleValue vfunction, HandleValue vscope, + HandleValue voptions, JSContext* cx, + MutableHandleValue rval) { + if (!xpc::ExportFunction(cx, vfunction, vscope, voptions, rval)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateObjectIn(HandleValue vobj, HandleValue voptions, + JSContext* cx, MutableHandleValue rval) { + RootedObject optionsObject( + cx, voptions.isObject() ? &voptions.toObject() : nullptr); + CreateObjectInOptions options(cx, optionsObject); + if (voptions.isObject() && !options.Parse()) { + return NS_ERROR_FAILURE; + } + + if (!xpc::CreateObjectIn(cx, vobj, options, rval)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::MakeObjectPropsNormal(HandleValue vobj, JSContext* cx) { + if (!cx) { + return NS_ERROR_FAILURE; + } + + // first argument must be an object + if (vobj.isPrimitive()) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + RootedObject obj(cx, js::UncheckedUnwrap(&vobj.toObject())); + JSAutoRealm ar(cx, obj); + Rooted<IdVector> ida(cx, IdVector(cx)); + if (!JS_Enumerate(cx, obj, &ida)) { + return NS_ERROR_FAILURE; + } + + RootedId id(cx); + RootedValue v(cx); + for (size_t i = 0; i < ida.length(); ++i) { + id = ida[i]; + + if (!JS_GetPropertyById(cx, obj, id, &v)) { + return NS_ERROR_FAILURE; + } + + if (v.isPrimitive()) { + continue; + } + + RootedObject propobj(cx, &v.toObject()); + // TODO Deal with non-functions. + if (!js::IsWrapper(propobj) || !JS::IsCallable(propobj)) { + continue; + } + + FunctionForwarderOptions forwarderOptions; + if (!NewFunctionForwarder(cx, id, propobj, forwarderOptions, &v) || + !JS_SetPropertyById(cx, obj, id, v)) + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsDeadWrapper(HandleValue obj, bool* out) { + *out = false; + if (obj.isPrimitive()) { + return NS_ERROR_INVALID_ARG; + } + + // We should never have cross-compartment wrappers for dead wrappers. + MOZ_ASSERT_IF(js::IsCrossCompartmentWrapper(&obj.toObject()), + !JS_IsDeadWrapper(js::UncheckedUnwrap(&obj.toObject()))); + + *out = JS_IsDeadWrapper(&obj.toObject()); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsRemoteProxy(HandleValue val, bool* out) { + if (val.isObject()) { + *out = dom::IsRemoteObjectProxy(UncheckedUnwrap(&val.toObject())); + ; + } else { + *out = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::RecomputeWrappers(HandleValue vobj, JSContext* cx) { + // Determine the compartment of the given object, if any. + JS::Compartment* c = + vobj.isObject() + ? JS::GetCompartment(js::UncheckedUnwrap(&vobj.toObject())) + : nullptr; + + // If no compartment was given, recompute all. + if (!c) { + js::RecomputeWrappers(cx, js::AllCompartments(), js::AllCompartments()); + // Otherwise, recompute wrappers for the given compartment. + } else { + js::RecomputeWrappers(cx, js::SingleCompartment(c), + js::AllCompartments()) && + js::RecomputeWrappers(cx, js::AllCompartments(), + js::SingleCompartment(c)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetWantXrays(HandleValue vscope, JSContext* cx) { + if (!vscope.isObject()) { + return NS_ERROR_INVALID_ARG; + } + JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); + MOZ_RELEASE_ASSERT(!AccessCheck::isChrome(scopeObj), + "Don't call setWantXrays on system-principal scopes"); + JS::Compartment* compartment = JS::GetCompartment(scopeObj); + CompartmentPrivate::Get(scopeObj)->wantXrays = true; + bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment), + js::AllCompartments()); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Dispatch(HandleValue runnableArg, HandleValue scope, + JSContext* cx) { + RootedValue runnable(cx, runnableArg); + // Enter the given realm, if any, and rewrap runnable. + Maybe<JSAutoRealm> ar; + if (scope.isObject()) { + JSObject* scopeObj = js::UncheckedUnwrap(&scope.toObject()); + if (!scopeObj) { + return NS_ERROR_FAILURE; + } + ar.emplace(cx, scopeObj); + if (!JS_WrapValue(cx, &runnable)) { + return NS_ERROR_FAILURE; + } + } + + // Get an XPCWrappedJS for |runnable|. + if (!runnable.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + RootedObject runnableObj(cx, &runnable.toObject()); + nsCOMPtr<nsIRunnable> run; + nsresult rv = nsXPConnect::XPConnect()->WrapJS( + cx, runnableObj, NS_GET_IID(nsIRunnable), getter_AddRefs(run)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(run); + + // Dispatch. + return NS_DispatchToMainThread(run); +} + +#define GENERATE_JSCONTEXTOPTION_GETTER_SETTER(_attr, _getter, _setter) \ + NS_IMETHODIMP \ + nsXPCComponents_Utils::Get##_attr(JSContext* cx, bool* aValue) { \ + *aValue = ContextOptionsRef(cx)._getter(); \ + return NS_OK; \ + } \ + NS_IMETHODIMP \ + nsXPCComponents_Utils::Set##_attr(JSContext* cx, bool aValue) { \ + ContextOptionsRef(cx)._setter(aValue); \ + return NS_OK; \ + } + +GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Strict_mode, strictMode, setStrictMode) + +#undef GENERATE_JSCONTEXTOPTION_GETTER_SETTER + +NS_IMETHODIMP +nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx) { +#ifdef JS_GC_ZEAL + JS_SetGCZeal(cx, uint8_t(aValue), JS_DEFAULT_ZEAL_FREQ); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetIsInAutomation(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = xpc::IsInAutomation(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ExitIfInAutomation() { + NS_ENSURE_TRUE(xpc::IsInAutomation(), NS_ERROR_FAILURE); + + profiler_shutdown(IsFastShutdown::Yes); + + mozilla::AppShutdown::DoImmediateExit(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CrashIfNotInAutomation() { + xpc::CrashIfNotInAutomation(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) { + AUTO_PROFILER_LABEL("nsXPCComponents_Utils::NukeSandbox", OTHER); + NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG); + JSObject* wrapper = &obj.toObject(); + NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG); + RootedObject sb(cx, UncheckedUnwrap(wrapper)); + NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG); + + xpc::NukeAllWrappersForRealm(cx, GetNonCCWObjectRealm(sb)); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg, + JSContext* cx) { + NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtWindowProxy = */ false)); + NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); + if (xpc::GetObjectPrincipal(global)->IsSystemPrincipal()) { + JS_ReportErrorASCII(cx, "Script may not be disabled for system globals"); + return NS_ERROR_FAILURE; + } + Scriptability::Get(global).Block(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnblockScriptForGlobal(HandleValue globalArg, + JSContext* cx) { + NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtWindowProxy = */ false)); + NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); + if (xpc::GetObjectPrincipal(global)->IsSystemPrincipal()) { + JS_ReportErrorASCII(cx, "Script may not be disabled for system globals"); + return NS_ERROR_FAILURE; + } + Scriptability::Get(global).Unblock(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsOpaqueWrapper(HandleValue obj, bool* aRetval) { + *aRetval = + obj.isObject() && xpc::WrapperFactory::IsOpaqueWrapper(&obj.toObject()); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsXrayWrapper(HandleValue obj, bool* aRetval) { + *aRetval = + obj.isObject() && xpc::WrapperFactory::IsXrayWrapper(&obj.toObject()); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::WaiveXrays(HandleValue aVal, JSContext* aCx, + MutableHandleValue aRetval) { + RootedValue value(aCx, aVal); + if (!xpc::WrapperFactory::WaiveXrayAndWrap(aCx, &value)) { + return NS_ERROR_FAILURE; + } + aRetval.set(value); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnwaiveXrays(HandleValue aVal, JSContext* aCx, + MutableHandleValue aRetval) { + if (!aVal.isObject()) { + aRetval.set(aVal); + return NS_OK; + } + + RootedObject obj(aCx, js::UncheckedUnwrap(&aVal.toObject())); + if (!JS_WrapObject(aCx, &obj)) { + return NS_ERROR_FAILURE; + } + aRetval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetClassName(HandleValue aObj, bool aUnwrap, + JSContext* aCx, char** aRv) { + if (!aObj.isObject()) { + return NS_ERROR_INVALID_ARG; + } + RootedObject obj(aCx, &aObj.toObject()); + if (aUnwrap) { + obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + } + *aRv = NS_xstrdup(JS::GetClass(obj)->name); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetDOMClassInfo(const nsAString& aClassName, + nsIClassInfo** aClassInfo) { + *aClassInfo = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetIncumbentGlobal(HandleValue aCallback, JSContext* aCx, + MutableHandleValue aOut) { + nsCOMPtr<nsIGlobalObject> global = mozilla::dom::GetIncumbentGlobal(); + RootedValue globalVal(aCx); + + if (!global) { + globalVal = NullValue(); + } else { + // Note: We rely on the wrap call for outerization. + globalVal = ObjectValue(*global->GetGlobalJSObject()); + if (!JS_WrapValue(aCx, &globalVal)) { + return NS_ERROR_FAILURE; + } + } + + // Invoke the callback, if passed. + if (aCallback.isObject()) { + RootedValue ignored(aCx); + if (!JS_CallFunctionValue(aCx, nullptr, aCallback, + JS::HandleValueArray(globalVal), &ignored)) { + return NS_ERROR_FAILURE; + } + } + + aOut.set(globalVal); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetDebugName(HandleValue aObj, JSContext* aCx, + nsACString& aOut) { + if (!aObj.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + RootedObject obj(aCx, &aObj.toObject()); + aOut = xpc::GetFunctionName(aCx, obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory, + PRTime* aOut) { + WatchdogTimestampCategory category; + if (aCategory.EqualsLiteral("ContextStateChange")) { + category = TimestampContextStateChange; + } else if (aCategory.EqualsLiteral("WatchdogWakeup")) { + category = TimestampWatchdogWakeup; + } else if (aCategory.EqualsLiteral("WatchdogHibernateStart")) { + category = TimestampWatchdogHibernateStart; + } else if (aCategory.EqualsLiteral("WatchdogHibernateStop")) { + category = TimestampWatchdogHibernateStop; + } else { + return NS_ERROR_INVALID_ARG; + } + *aOut = XPCJSContext::Get()->GetWatchdogTimestamp(category); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetJSEngineTelemetryValue(JSContext* cx, + MutableHandleValue rval) { + RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // No JS engine telemetry in use at the moment. + + rval.setObject(*obj); + return NS_OK; +} + +bool xpc::CloneInto(JSContext* aCx, HandleValue aValue, HandleValue aScope, + HandleValue aOptions, MutableHandleValue aCloned) { + if (!aScope.isObject()) { + return false; + } + + RootedObject scope(aCx, &aScope.toObject()); + // The scope could be a Window, so we need to CheckedUnwrapDynamic. + scope = js::CheckedUnwrapDynamic(scope, aCx); + if (!scope) { + JS_ReportErrorASCII(aCx, "Permission denied to clone object into scope"); + return false; + } + + if (!aOptions.isUndefined() && !aOptions.isObject()) { + JS_ReportErrorASCII(aCx, "Invalid argument"); + return false; + } + + RootedObject optionsObject( + aCx, aOptions.isObject() ? &aOptions.toObject() : nullptr); + StackScopedCloneOptions options(aCx, optionsObject); + if (aOptions.isObject() && !options.Parse()) { + return false; + } + + js::AssertSameCompartment(aCx, aValue); + RootedObject sourceScope(aCx, JS::CurrentGlobalOrNull(aCx)); + + { + JSAutoRealm ar(aCx, scope); + aCloned.set(aValue); + if (!StackScopedClone(aCx, options, sourceScope, aCloned)) { + return false; + } + } + + return JS_WrapValue(aCx, aCloned); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CloneInto(HandleValue aValue, HandleValue aScope, + HandleValue aOptions, JSContext* aCx, + MutableHandleValue aCloned) { + return xpc::CloneInto(aCx, aValue, aScope, aOptions, aCloned) + ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWebIDLCallerPrincipal(nsIPrincipal** aResult) { + // This API may only be when the Entry Settings Object corresponds to a + // JS-implemented WebIDL call. In all other cases, the value will be null, + // and we throw. + nsCOMPtr<nsIPrincipal> callerPrin = mozilla::dom::GetWebIDLCallerPrincipal(); + if (!callerPrin) { + return NS_ERROR_NOT_AVAILABLE; + } + callerPrin.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetObjectPrincipal(HandleValue val, JSContext* cx, + nsIPrincipal** result) { + if (!val.isObject()) { + return NS_ERROR_INVALID_ARG; + } + RootedObject obj(cx, &val.toObject()); + // We need to be able to unwrap to WindowProxy or Location here, so + // use CheckedUnwrapDynamic. + obj = js::CheckedUnwrapDynamic(obj, cx); + MOZ_ASSERT(obj); + + nsCOMPtr<nsIPrincipal> prin = nsContentUtils::ObjectPrincipal(obj); + prin.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetRealmLocation(HandleValue val, JSContext* cx, + nsACString& result) { + if (!val.isObject()) { + return NS_ERROR_INVALID_ARG; + } + RootedObject obj(cx, &val.toObject()); + // We need to be able to unwrap to WindowProxy or Location here, so + // use CheckedUnwrapDynamic. + obj = js::CheckedUnwrapDynamic(obj, cx); + MOZ_ASSERT(obj); + + result = xpc::RealmPrivate::Get(obj)->GetLocation(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ReadUTF8File(nsIFile* aFile, nsACString& aResult) { + NS_ENSURE_TRUE(aFile, NS_ERROR_INVALID_ARG); + + MOZ_TRY_VAR(aResult, URLPreloader::ReadFile(aFile)); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ReadUTF8URI(nsIURI* aURI, nsACString& aResult) { + NS_ENSURE_TRUE(aURI, NS_ERROR_INVALID_ARG); + + MOZ_TRY_VAR(aResult, URLPreloader::ReadURI(aURI)); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Now(double* aRetval) { + TimeStamp start = TimeStamp::ProcessCreation(); + *aRetval = (TimeStamp::Now() - start).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateSpellChecker(nsIEditorSpellCheck** aSpellChecker) { + NS_ENSURE_ARG_POINTER(aSpellChecker); + nsCOMPtr<nsIEditorSpellCheck> spellChecker = new mozilla::EditorSpellCheck(); + spellChecker.forget(aSpellChecker); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateCommandLine(const nsTArray<nsCString>& aArgs, + nsIFile* aWorkingDir, uint32_t aState, + nsISupports** aCommandLine) { + NS_ENSURE_ARG_MAX(aState, nsICommandLine::STATE_REMOTE_EXPLICIT); + NS_ENSURE_ARG_POINTER(aCommandLine); + + nsCOMPtr<nsISupports> commandLine = new nsCommandLine(); + nsCOMPtr<nsICommandLineRunner> runner = do_QueryInterface(commandLine); + + nsTArray<const char*> fakeArgv(aArgs.Length() + 2); + + // Prepend a dummy argument for the program name, which will be ignored. + fakeArgv.AppendElement(nullptr); + for (const nsCString& arg : aArgs) { + fakeArgv.AppendElement(arg.get()); + } + // Append a null terminator. + fakeArgv.AppendElement(nullptr); + + nsresult rv = runner->Init(fakeArgv.Length() - 1, fakeArgv.Elements(), + aWorkingDir, aState); + NS_ENSURE_SUCCESS(rv, rv); + + commandLine.forget(aCommandLine); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateCommandParams(nsICommandParams** aCommandParams) { + NS_ENSURE_ARG_POINTER(aCommandParams); + nsCOMPtr<nsICommandParams> commandParams = new nsCommandParams(); + commandParams.forget(aCommandParams); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateLoadContext(nsILoadContext** aLoadContext) { + NS_ENSURE_ARG_POINTER(aLoadContext); + nsCOMPtr<nsILoadContext> loadContext = ::CreateLoadContext(); + loadContext.forget(aLoadContext); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreatePrivateLoadContext(nsILoadContext** aLoadContext) { + NS_ENSURE_ARG_POINTER(aLoadContext); + nsCOMPtr<nsILoadContext> loadContext = ::CreatePrivateLoadContext(); + loadContext.forget(aLoadContext); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreatePersistentProperties( + nsIPersistentProperties** aPersistentProperties) { + NS_ENSURE_ARG_POINTER(aPersistentProperties); + nsCOMPtr<nsIPersistentProperties> props = new nsPersistentProperties(); + props.forget(aPersistentProperties); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateDocumentEncoder( + const char* aContentType, nsIDocumentEncoder** aDocumentEncoder) { + NS_ENSURE_ARG_POINTER(aDocumentEncoder); + nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder(aContentType); + encoder.forget(aDocumentEncoder); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateHTMLCopyEncoder( + nsIDocumentEncoder** aDocumentEncoder) { + NS_ENSURE_ARG_POINTER(aDocumentEncoder); + nsCOMPtr<nsIDocumentEncoder> encoder = do_createHTMLCopyEncoder(); + encoder.forget(aDocumentEncoder); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetLoadedModules(nsTArray<nsCString>& aLoadedModules) { + return mozJSModuleLoader::Get()->GetLoadedJSAndESModules(aLoadedModules); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetLoadedJSModules( + nsTArray<nsCString>& aLoadedJSModules) { + mozJSModuleLoader::Get()->GetLoadedModules(aLoadedJSModules); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetLoadedESModules( + nsTArray<nsCString>& aLoadedESModules) { + return mozJSModuleLoader::Get()->GetLoadedESModules(aLoadedESModules); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetModuleImportStack(const nsACString& aLocation, + nsACString& aRetval) { + nsresult rv = + mozJSModuleLoader::Get()->GetModuleImportStack(aLocation, aRetval); + // Fallback the query to the DevTools loader if not found in the shared loader + if (rv == NS_ERROR_FAILURE && mozJSModuleLoader::GetDevToolsLoader()) { + return mozJSModuleLoader::GetDevToolsLoader()->GetModuleImportStack( + aLocation, aRetval); + } + return rv; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +nsXPCComponents::nsXPCComponents(XPCWrappedNativeScope* aScope) + : mScope(aScope) { + MOZ_ASSERT(aScope, "aScope must not be null"); +} + +nsXPCComponents::~nsXPCComponents() = default; + +void nsXPCComponents::ClearMembers() { + mInterfaces = nullptr; + mResults = nullptr; + mClasses = nullptr; + mID = nullptr; + mException = nullptr; + mConstructor = nullptr; + mUtils = nullptr; +} + +/*******************************************/ +#define XPC_IMPL_GET_OBJ_METHOD(_class, _n) \ + NS_IMETHODIMP _class::Get##_n(nsIXPCComponents_##_n** a##_n) { \ + NS_ENSURE_ARG_POINTER(a##_n); \ + if (!m##_n) m##_n = new nsXPCComponents_##_n(); \ + RefPtr<nsXPCComponents_##_n> ret = m##_n; \ + ret.forget(a##_n); \ + return NS_OK; \ + } + +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Interfaces) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Classes) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Results) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, ID) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Exception) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Constructor) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Utils) + +#undef XPC_IMPL_GET_OBJ_METHOD +/*******************************************/ + +NS_IMETHODIMP +nsXPCComponents::IsSuccessCode(nsresult result, bool* out) { + *out = NS_SUCCEEDED(result); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents::GetStack(nsIStackFrame** aStack) { + nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(); + frame.forget(aStack); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents::GetManager(nsIComponentManager** aManager) { + MOZ_ASSERT(aManager, "bad param"); + return NS_GetComponentManager(aManager); +} + +NS_IMETHODIMP +nsXPCComponents::GetReturnCode(JSContext* aCx, MutableHandleValue aOut) { + nsresult res = XPCJSContext::Get()->GetPendingResult(); + aOut.setNumber(static_cast<uint32_t>(res)); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents::SetReturnCode(JSContext* aCx, HandleValue aCode) { + nsresult rv; + if (!ToUint32(aCx, aCode, (uint32_t*)&rv)) { + return NS_ERROR_FAILURE; + } + XPCJSContext::Get()->SetPendingResult(rv); + return NS_OK; +} + +/**********************************************/ + +class ComponentsSH : public nsIXPCScriptable { + public: + explicit constexpr ComponentsSH(unsigned dummy) {} + + // We don't actually inherit any ref counting infrastructure, but we don't + // need an nsAutoRefCnt member, so the _INHERITED macro is a hack to avoid + // having one. + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIXPCSCRIPTABLE + static nsresult Get(nsIXPCScriptable** helper) { + *helper = &singleton; + return NS_OK; + } + + private: + static ComponentsSH singleton; +}; + +ComponentsSH ComponentsSH::singleton(0); + +// Singleton refcounting. +NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::AddRef(void) { return 1; } +NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::Release(void) { + return 1; +} + +NS_IMPL_QUERY_INTERFACE(ComponentsSH, nsIXPCScriptable) + +#define NSXPCCOMPONENTS_CID \ + { \ + 0x3649f405, 0xf0ec, 0x4c28, { \ + 0xae, 0xb0, 0xaf, 0x9a, 0x51, 0xe4, 0x4c, 0x81 \ + } \ + } + +NS_IMPL_CLASSINFO(nsXPCComponents, &ComponentsSH::Get, 0, NSXPCCOMPONENTS_CID) +NS_IMPL_ISUPPORTS_CI(nsXPCComponents, nsIXPCComponents) + +// The nsIXPCScriptable map declaration that will generate stubs for us +#define XPC_MAP_CLASSNAME ComponentsSH +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents" +#define XPC_MAP_FLAGS XPC_SCRIPTABLE_WANT_PRECREATE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +ComponentsSH::PreCreate(nsISupports* nativeObj, JSContext* cx, + JSObject* globalObj, JSObject** parentObj) { + nsXPCComponents* self = static_cast<nsXPCComponents*>(nativeObj); + // this should never happen + if (!self->GetScope()) { + NS_WARNING( + "mScope must not be null when nsXPCComponents::PreCreate is called"); + return NS_ERROR_FAILURE; + } + *parentObj = self->GetScope()->GetGlobalForWrappedNatives(); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp new file mode 100644 index 0000000000..9c6fd75eec --- /dev/null +++ b/js/xpconnect/src/XPCConvert.cpp @@ -0,0 +1,1649 @@ +/* -*- 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/. */ + +/* Data conversion between native and JavaScript types. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Range.h" +#include "mozilla/Sprintf.h" + +#include "xpcprivate.h" +#include "nsIScriptError.h" +#include "nsISimpleEnumerator.h" +#include "nsWrapperCache.h" +#include "nsJSUtils.h" +#include "nsQueryObject.h" +#include "nsScriptError.h" +#include "WrapperFactory.h" + +#include "nsWrapperCacheInlines.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/CharacterEncoding.h" +#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType, JS_GetArrayBufferViewData, JS_GetTypedArrayLength, JS_IsTypedArrayObject +#include "js/MemoryFunctions.h" +#include "js/Object.h" // JS::GetClass +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetElement +#include "js/String.h" // JS::StringHasLatin1Chars + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/Promise.h" + +using namespace xpc; +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +// #define STRICT_CHECK_OF_UNICODE +#ifdef STRICT_CHECK_OF_UNICODE +# define ILLEGAL_RANGE(c) (0 != ((c)&0xFF80)) +#else // STRICT_CHECK_OF_UNICODE +# define ILLEGAL_RANGE(c) (0 != ((c)&0xFF00)) +#endif // STRICT_CHECK_OF_UNICODE + +#define ILLEGAL_CHAR_RANGE(c) (0 != ((c)&0x80)) + +/***************************************************************************/ + +// static +bool XPCConvert::GetISupportsFromJSObject(JSObject* obj, nsISupports** iface) { + if (JS::GetClass(obj)->slot0IsISupports()) { + *iface = JS::GetObjectISupports<nsISupports>(obj); + return true; + } + *iface = UnwrapDOMObjectToISupports(obj); + return !!*iface; +} + +/***************************************************************************/ + +// static +bool XPCConvert::NativeData2JS(JSContext* cx, MutableHandleValue d, + const void* s, const nsXPTType& type, + const nsID* iid, uint32_t arrlen, + nsresult* pErr) { + MOZ_ASSERT(s, "bad param"); + + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + } + + switch (type.Tag()) { + case nsXPTType::T_I8: + d.setInt32(*static_cast<const int8_t*>(s)); + return true; + case nsXPTType::T_I16: + d.setInt32(*static_cast<const int16_t*>(s)); + return true; + case nsXPTType::T_I32: + d.setInt32(*static_cast<const int32_t*>(s)); + return true; + case nsXPTType::T_I64: + d.setNumber(static_cast<double>(*static_cast<const int64_t*>(s))); + return true; + case nsXPTType::T_U8: + d.setInt32(*static_cast<const uint8_t*>(s)); + return true; + case nsXPTType::T_U16: + d.setInt32(*static_cast<const uint16_t*>(s)); + return true; + case nsXPTType::T_U32: + d.setNumber(*static_cast<const uint32_t*>(s)); + return true; + case nsXPTType::T_U64: + d.setNumber(static_cast<double>(*static_cast<const uint64_t*>(s))); + return true; + case nsXPTType::T_FLOAT: + d.setNumber(*static_cast<const float*>(s)); + return true; + case nsXPTType::T_DOUBLE: + d.set(JS_NumberValue(*static_cast<const double*>(s))); + return true; + case nsXPTType::T_BOOL: + d.setBoolean(*static_cast<const bool*>(s)); + return true; + case nsXPTType::T_CHAR: { + char p = *static_cast<const char*>(s); + +#ifdef STRICT_CHECK_OF_UNICODE + MOZ_ASSERT(!ILLEGAL_CHAR_RANGE(p), "passing non ASCII data"); +#endif // STRICT_CHECK_OF_UNICODE + + JSString* str = JS_NewStringCopyN(cx, &p, 1); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + case nsXPTType::T_WCHAR: { + char16_t p = *static_cast<const char16_t*>(s); + + JSString* str = JS_NewUCStringCopyN(cx, &p, 1); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + + case nsXPTType::T_JSVAL: { + d.set(*static_cast<const Value*>(s)); + return JS_WrapValue(cx, d); + } + + case nsXPTType::T_VOID: + XPC_LOG_ERROR(("XPCConvert::NativeData2JS : void* params not supported")); + return false; + + case nsXPTType::T_NSIDPTR: { + nsID* iid2 = *static_cast<nsID* const*>(s); + if (!iid2) { + d.setNull(); + return true; + } + + return xpc::ID2JSValue(cx, *iid2, d); + } + + case nsXPTType::T_NSID: + return xpc::ID2JSValue(cx, *static_cast<const nsID*>(s), d); + + case nsXPTType::T_ASTRING: { + const nsAString* p = static_cast<const nsAString*>(s); + if (!p || p->IsVoid()) { + d.setNull(); + return true; + } + + nsStringBuffer* buf; + if (!XPCStringConvert::ReadableToJSVal(cx, *p, &buf, d)) { + return false; + } + if (buf) { + buf->AddRef(); + } + return true; + } + + case nsXPTType::T_CHAR_STR: { + const char* p = *static_cast<const char* const*>(s); + arrlen = p ? strlen(p) : 0; + [[fallthrough]]; + } + case nsXPTType::T_PSTRING_SIZE_IS: { + const char* p = *static_cast<const char* const*>(s); + if (!p) { + d.setNull(); + return true; + } + +#ifdef STRICT_CHECK_OF_UNICODE + bool isAscii = true; + for (uint32_t i = 0; i < arrlen; i++) { + if (ILLEGAL_CHAR_RANGE(p[i])) { + isAscii = false; + } + } + MOZ_ASSERT(isAscii, "passing non ASCII data"); +#endif // STRICT_CHECK_OF_UNICODE + + JSString* str = JS_NewStringCopyN(cx, p, arrlen); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + + case nsXPTType::T_WCHAR_STR: { + const char16_t* p = *static_cast<const char16_t* const*>(s); + arrlen = p ? nsCharTraits<char16_t>::length(p) : 0; + [[fallthrough]]; + } + case nsXPTType::T_PWSTRING_SIZE_IS: { + const char16_t* p = *static_cast<const char16_t* const*>(s); + if (!p) { + d.setNull(); + return true; + } + + JSString* str = JS_NewUCStringCopyN(cx, p, arrlen); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + + case nsXPTType::T_UTF8STRING: { + const nsACString* utf8String = static_cast<const nsACString*>(s); + + if (!utf8String || utf8String->IsVoid()) { + d.setNull(); + return true; + } + + if (utf8String->IsEmpty()) { + d.set(JS_GetEmptyStringValue(cx)); + return true; + } + + uint32_t len = utf8String->Length(); + auto allocLen = CheckedUint32(len) + 1; + if (!allocLen.isValid()) { + return false; + } + + // Usage of UTF-8 in XPConnect is mostly for things that are + // almost always ASCII, so the inexact allocations below + // should be fine. + + if (IsUtf8Latin1(*utf8String)) { + using UniqueLatin1Chars = + js::UniquePtr<JS::Latin1Char[], JS::FreePolicy>; + + UniqueLatin1Chars buffer(static_cast<JS::Latin1Char*>( + JS_string_malloc(cx, allocLen.value()))); + if (!buffer) { + return false; + } + + size_t written = LossyConvertUtf8toLatin1( + *utf8String, Span(reinterpret_cast<char*>(buffer.get()), len)); + buffer[written] = 0; + + // written can never exceed len, so the truncation is OK. + JSString* str = JS_NewLatin1String(cx, std::move(buffer), written); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + + // 1-byte sequences decode to 1 UTF-16 code unit + // 2-byte sequences decode to 1 UTF-16 code unit + // 3-byte sequences decode to 1 UTF-16 code unit + // 4-byte sequences decode to 2 UTF-16 code units + // So the number of output code units never exceeds + // the number of input code units (but see the comment + // below). allocLen already takes the zero terminator + // into account. + allocLen *= sizeof(char16_t); + if (!allocLen.isValid()) { + return false; + } + + JS::UniqueTwoByteChars buffer( + static_cast<char16_t*>(JS_string_malloc(cx, allocLen.value()))); + if (!buffer) { + return false; + } + + // For its internal simplicity, ConvertUTF8toUTF16 requires the + // destination to be one code unit longer than the source, but + // it never actually writes more code units than the number of + // code units in the source. That's why it's OK to claim the + // output buffer has len + 1 space but then still expect to + // have space for the zero terminator. + size_t written = + ConvertUtf8toUtf16(*utf8String, Span(buffer.get(), allocLen.value())); + MOZ_RELEASE_ASSERT(written <= len); + buffer[written] = 0; + + JSString* str = JS_NewUCStringDontDeflate(cx, std::move(buffer), written); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + case nsXPTType::T_CSTRING: { + const nsACString* cString = static_cast<const nsACString*>(s); + + if (!cString || cString->IsVoid()) { + d.setNull(); + return true; + } + + // c-strings (binary blobs) are deliberately not converted from + // UTF-8 to UTF-16. T_UTF8Sting is for UTF-8 encoded strings + // with automatic conversion. + JSString* str = JS_NewStringCopyN(cx, cString->Data(), cString->Length()); + if (!str) { + return false; + } + + d.setString(str); + return true; + } + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: { + nsISupports* iface = *static_cast<nsISupports* const*>(s); + if (!iface) { + d.setNull(); + return true; + } + + if (iid->Equals(NS_GET_IID(nsIVariant))) { + nsCOMPtr<nsIVariant> variant = do_QueryInterface(iface); + if (!variant) { + return false; + } + + return XPCVariant::VariantDataToJS(cx, variant, pErr, d); + } + + xpcObjectHelper helper(iface); + return NativeInterface2JSObject(cx, d, helper, iid, true, pErr); + } + + case nsXPTType::T_DOMOBJECT: { + void* ptr = *static_cast<void* const*>(s); + if (!ptr) { + d.setNull(); + return true; + } + + return type.GetDOMObjectInfo().Wrap(cx, ptr, d); + } + + case nsXPTType::T_PROMISE: { + Promise* promise = *static_cast<Promise* const*>(s); + if (!promise) { + d.setNull(); + return true; + } + + RootedObject jsobj(cx, promise->PromiseObj()); + if (!JS_WrapObject(cx, &jsobj)) { + return false; + } + d.setObject(*jsobj); + return true; + } + + case nsXPTType::T_LEGACY_ARRAY: + return NativeArray2JS(cx, d, *static_cast<const void* const*>(s), + type.ArrayElementType(), iid, arrlen, pErr); + + case nsXPTType::T_ARRAY: { + auto* array = static_cast<const xpt::detail::UntypedTArray*>(s); + return NativeArray2JS(cx, d, array->Elements(), type.ArrayElementType(), + iid, array->Length(), pErr); + } + + default: + NS_ERROR("bad type"); + return false; + } +} + +/***************************************************************************/ + +#ifdef DEBUG +static bool CheckChar16InCharRange(char16_t c) { + if (ILLEGAL_RANGE(c)) { + /* U+0080/U+0100 - U+FFFF data lost. */ + static const size_t MSG_BUF_SIZE = 64; + char msg[MSG_BUF_SIZE]; + SprintfLiteral(msg, + "char16_t out of char range; high bits of data lost: 0x%x", + int(c)); + NS_WARNING(msg); + return false; + } + + return true; +} + +template <typename CharT> +static void CheckCharsInCharRange(const CharT* chars, size_t len) { + for (size_t i = 0; i < len; i++) { + if (!CheckChar16InCharRange(chars[i])) { + break; + } + } +} +#endif + +template <typename T> +bool ConvertToPrimitive(JSContext* cx, HandleValue v, T* retval) { + return ValueToPrimitive<T, eDefault>(cx, v, "Value", retval); +} + +// static +bool XPCConvert::JSData2Native(JSContext* cx, void* d, HandleValue s, + const nsXPTType& type, const nsID* iid, + uint32_t arrlen, nsresult* pErr) { + MOZ_ASSERT(d, "bad param"); + + js::AssertSameCompartment(cx, s); + + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + } + + bool sizeis = + type.Tag() == TD_PSTRING_SIZE_IS || type.Tag() == TD_PWSTRING_SIZE_IS; + + switch (type.Tag()) { + case nsXPTType::T_I8: + return ConvertToPrimitive(cx, s, static_cast<int8_t*>(d)); + case nsXPTType::T_I16: + return ConvertToPrimitive(cx, s, static_cast<int16_t*>(d)); + case nsXPTType::T_I32: + return ConvertToPrimitive(cx, s, static_cast<int32_t*>(d)); + case nsXPTType::T_I64: + return ConvertToPrimitive(cx, s, static_cast<int64_t*>(d)); + case nsXPTType::T_U8: + return ConvertToPrimitive(cx, s, static_cast<uint8_t*>(d)); + case nsXPTType::T_U16: + return ConvertToPrimitive(cx, s, static_cast<uint16_t*>(d)); + case nsXPTType::T_U32: + return ConvertToPrimitive(cx, s, static_cast<uint32_t*>(d)); + case nsXPTType::T_U64: + return ConvertToPrimitive(cx, s, static_cast<uint64_t*>(d)); + case nsXPTType::T_FLOAT: + return ConvertToPrimitive(cx, s, static_cast<float*>(d)); + case nsXPTType::T_DOUBLE: + return ConvertToPrimitive(cx, s, static_cast<double*>(d)); + case nsXPTType::T_BOOL: + return ConvertToPrimitive(cx, s, static_cast<bool*>(d)); + case nsXPTType::T_CHAR: { + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + char16_t ch; + if (JS_GetStringLength(str) == 0) { + ch = 0; + } else { + if (!JS_GetStringCharAt(cx, str, 0, &ch)) { + return false; + } + } +#ifdef DEBUG + CheckChar16InCharRange(ch); +#endif + *((char*)d) = char(ch); + break; + } + case nsXPTType::T_WCHAR: { + JSString* str; + if (!(str = ToString(cx, s))) { + return false; + } + size_t length = JS_GetStringLength(str); + if (length == 0) { + *((uint16_t*)d) = 0; + break; + } + + char16_t ch; + if (!JS_GetStringCharAt(cx, str, 0, &ch)) { + return false; + } + + *((uint16_t*)d) = uint16_t(ch); + break; + } + case nsXPTType::T_JSVAL: + *((Value*)d) = s; + break; + case nsXPTType::T_VOID: + XPC_LOG_ERROR(("XPCConvert::JSData2Native : void* params not supported")); + NS_ERROR("void* params not supported"); + return false; + + case nsXPTType::T_NSIDPTR: + if (Maybe<nsID> id = xpc::JSValue2ID(cx, s)) { + *((const nsID**)d) = id.ref().Clone(); + return true; + } + return false; + + case nsXPTType::T_NSID: + if (Maybe<nsID> id = xpc::JSValue2ID(cx, s)) { + *((nsID*)d) = id.ref(); + return true; + } + return false; + + case nsXPTType::T_ASTRING: { + nsAString* ws = (nsAString*)d; + if (s.isUndefined() || s.isNull()) { + ws->SetIsVoid(true); + return true; + } + size_t length = 0; + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + length = JS_GetStringLength(str); + if (!length) { + ws->Truncate(); + return true; + } + + return AssignJSString(cx, *ws, str); + } + + case nsXPTType::T_CHAR_STR: + case nsXPTType::T_PSTRING_SIZE_IS: { + if (s.isUndefined() || s.isNull()) { + if (sizeis && 0 != arrlen) { + if (pErr) { + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + } + return false; + } + *((char**)d) = nullptr; + return true; + } + + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + +#ifdef DEBUG + if (JS::StringHasLatin1Chars(str)) { + size_t len; + AutoCheckCannotGC nogc; + const Latin1Char* chars = + JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len); + if (chars) { + CheckCharsInCharRange(chars, len); + } + } else { + size_t len; + AutoCheckCannotGC nogc; + const char16_t* chars = + JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len); + if (chars) { + CheckCharsInCharRange(chars, len); + } + } +#endif // DEBUG + + size_t length = JS_GetStringEncodingLength(cx, str); + if (length == size_t(-1)) { + return false; + } + if (sizeis) { + if (length > arrlen) { + if (pErr) { + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + } + return false; + } + if (length < arrlen) { + length = arrlen; + } + } + char* buffer = static_cast<char*>(moz_xmalloc(length + 1)); + if (!JS_EncodeStringToBuffer(cx, str, buffer, length)) { + free(buffer); + return false; + } + buffer[length] = '\0'; + *((void**)d) = buffer; + return true; + } + + case nsXPTType::T_WCHAR_STR: + case nsXPTType::T_PWSTRING_SIZE_IS: { + JSString* str; + + if (s.isUndefined() || s.isNull()) { + if (sizeis && 0 != arrlen) { + if (pErr) { + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + } + return false; + } + *((char16_t**)d) = nullptr; + return true; + } + + if (!(str = ToString(cx, s))) { + return false; + } + size_t len = JS_GetStringLength(str); + if (sizeis) { + if (len > arrlen) { + if (pErr) { + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + } + return false; + } + if (len < arrlen) { + len = arrlen; + } + } + + size_t byte_len = (len + 1) * sizeof(char16_t); + *((void**)d) = moz_xmalloc(byte_len); + mozilla::Range<char16_t> destChars(*((char16_t**)d), len + 1); + if (!JS_CopyStringChars(cx, destChars, str)) { + return false; + } + destChars[len] = 0; + + return true; + } + + case nsXPTType::T_UTF8STRING: { + nsACString* rs = (nsACString*)d; + if (s.isNull() || s.isUndefined()) { + rs->SetIsVoid(true); + return true; + } + + // The JS val is neither null nor void... + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + size_t length = JS_GetStringLength(str); + if (!length) { + rs->Truncate(); + return true; + } + + JSLinearString* linear = JS_EnsureLinearString(cx, str); + if (!linear) { + return false; + } + + size_t utf8Length = JS::GetDeflatedUTF8StringLength(linear); + if (!rs->SetLength(utf8Length, fallible)) { + if (pErr) { + *pErr = NS_ERROR_OUT_OF_MEMORY; + } + return false; + } + + mozilla::DebugOnly<size_t> written = JS::DeflateStringToUTF8Buffer( + linear, mozilla::Span(rs->BeginWriting(), utf8Length)); + MOZ_ASSERT(written == utf8Length); + + return true; + } + + case nsXPTType::T_CSTRING: { + nsACString* rs = (nsACString*)d; + if (s.isNull() || s.isUndefined()) { + rs->SetIsVoid(true); + return true; + } + + // The JS val is neither null nor void... + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + size_t length = JS_GetStringEncodingLength(cx, str); + if (length == size_t(-1)) { + return false; + } + + if (!length) { + rs->Truncate(); + return true; + } + + if (!rs->SetLength(uint32_t(length), fallible)) { + if (pErr) { + *pErr = NS_ERROR_OUT_OF_MEMORY; + } + return false; + } + if (rs->Length() != uint32_t(length)) { + return false; + } + if (!JS_EncodeStringToBuffer(cx, str, rs->BeginWriting(), length)) { + return false; + } + + return true; + } + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: { + MOZ_ASSERT(iid, "can't do interface conversions without iid"); + + if (iid->Equals(NS_GET_IID(nsIVariant))) { + nsCOMPtr<nsIVariant> variant = XPCVariant::newVariant(cx, s); + if (!variant) { + return false; + } + + variant.forget(static_cast<nsISupports**>(d)); + return true; + } + + if (s.isNullOrUndefined()) { + *((nsISupports**)d) = nullptr; + return true; + } + + // only wrap JSObjects + if (!s.isObject()) { + if (pErr && s.isInt32() && 0 == s.toInt32()) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL; + } + return false; + } + + RootedObject src(cx, &s.toObject()); + return JSObject2NativeInterface(cx, (void**)d, src, iid, nullptr, pErr); + } + + case nsXPTType::T_DOMOBJECT: { + if (s.isNullOrUndefined()) { + *((void**)d) = nullptr; + return true; + } + + // Can't handle non-JSObjects + if (!s.isObject()) { + return false; + } + + nsresult err = type.GetDOMObjectInfo().Unwrap(s, (void**)d, cx); + if (pErr) { + *pErr = err; + } + return NS_SUCCEEDED(err); + } + + case nsXPTType::T_PROMISE: { + nsIGlobalObject* glob = CurrentNativeGlobal(cx); + if (!glob) { + if (pErr) { + *pErr = NS_ERROR_UNEXPECTED; + } + return false; + } + + // Call Promise::Resolve to create a Promise object. This allows us to + // support returning non-promise values from Promise-returning functions + // in JS. + IgnoredErrorResult err; + *(Promise**)d = Promise::Resolve(glob, cx, s, err).take(); + bool ok = !err.Failed(); + if (pErr) { + *pErr = err.StealNSResult(); + } + + return ok; + } + + case nsXPTType::T_LEGACY_ARRAY: { + void** dest = (void**)d; + const nsXPTType& elty = type.ArrayElementType(); + + *dest = nullptr; + + // FIXME: XPConnect historically has shortcut the JSArray2Native codepath + // in its caller if arrlen is 0, allowing arbitrary values to be passed as + // arrays and interpreted as the empty array (bug 1458987). + // + // NOTE: Once this is fixed, null/undefined should be allowed for arrays + // if arrlen is 0. + if (arrlen == 0) { + return true; + } + + bool ok = JSArray2Native( + cx, s, elty, iid, pErr, [&](uint32_t* aLength) -> void* { + // Check that we have enough elements in our array. + if (*aLength < arrlen) { + if (pErr) { + *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; + } + return nullptr; + } + *aLength = arrlen; + + // Allocate the backing buffer & return it. + *dest = moz_xmalloc(*aLength * elty.Stride()); + return *dest; + }); + + if (!ok && *dest) { + // An error occurred, free any allocated backing buffer. + free(*dest); + *dest = nullptr; + } + return ok; + } + + case nsXPTType::T_ARRAY: { + auto* dest = (xpt::detail::UntypedTArray*)d; + const nsXPTType& elty = type.ArrayElementType(); + + bool ok = JSArray2Native(cx, s, elty, iid, pErr, + [&](uint32_t* aLength) -> void* { + if (!dest->SetLength(elty, *aLength)) { + if (pErr) { + *pErr = NS_ERROR_OUT_OF_MEMORY; + } + return nullptr; + } + return dest->Elements(); + }); + + if (!ok) { + // An error occurred, free any allocated backing buffer. + dest->Clear(); + } + return ok; + } + + default: + NS_ERROR("bad type"); + return false; + } + return true; +} + +/***************************************************************************/ +// static +bool XPCConvert::NativeInterface2JSObject(JSContext* cx, MutableHandleValue d, + xpcObjectHelper& aHelper, + const nsID* iid, + bool allowNativeWrapper, + nsresult* pErr) { + if (!iid) { + iid = &NS_GET_IID(nsISupports); + } + + d.setNull(); + if (!aHelper.Object()) { + return true; + } + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + } + + // We used to have code here that unwrapped and simply exposed the + // underlying JSObject. That caused anomolies when JSComponents were + // accessed from other JS code - they didn't act like other xpconnect + // wrapped components. So, instead, we create "double wrapped" objects + // (that means an XPCWrappedNative around an nsXPCWrappedJS). This isn't + // optimal -- we could detect this and roll the functionality into a + // single wrapper, but the current solution is good enough for now. + XPCWrappedNativeScope* xpcscope = ObjectScope(JS::CurrentGlobalOrNull(cx)); + if (!xpcscope) { + return false; + } + + JSAutoRealm ar(cx, xpcscope->GetGlobalForWrappedNatives()); + + // First, see if this object supports the wrapper cache. In that case, the + // object to use is found as cache->GetWrapper(). If that is null, then the + // object will create (and fill the cache) from its WrapObject call. + nsWrapperCache* cache = aHelper.GetWrapperCache(); + + RootedObject flat(cx, cache ? cache->GetWrapper() : nullptr); + if (!flat && cache) { + RootedObject global(cx, CurrentGlobalOrNull(cx)); + flat = cache->WrapObject(cx, nullptr); + if (!flat) { + return false; + } + } + if (flat) { + if (allowNativeWrapper && !JS_WrapObject(cx, &flat)) { + return false; + } + d.setObjectOrNull(flat); + return true; + } + + // Go ahead and create an XPCWrappedNative for this object. + RefPtr<XPCNativeInterface> iface = XPCNativeInterface::GetNewOrUsed(cx, iid); + if (!iface) { + return false; + } + + RefPtr<XPCWrappedNative> wrapper; + nsresult rv = XPCWrappedNative::GetNewOrUsed(cx, aHelper, xpcscope, iface, + getter_AddRefs(wrapper)); + if (NS_FAILED(rv) && pErr) { + *pErr = rv; + } + + // If creating the wrapped native failed, then return early. + if (NS_FAILED(rv) || !wrapper) { + return false; + } + + // If we're not creating security wrappers, we can return the + // XPCWrappedNative as-is here. + flat = wrapper->GetFlatJSObject(); + if (!allowNativeWrapper) { + d.setObjectOrNull(flat); + if (pErr) { + *pErr = NS_OK; + } + return true; + } + + // The call to wrap here handles both cross-compartment and same-compartment + // security wrappers. + RootedObject original(cx, flat); + if (!JS_WrapObject(cx, &flat)) { + return false; + } + + d.setObjectOrNull(flat); + + if (pErr) { + *pErr = NS_OK; + } + + return true; +} + +/***************************************************************************/ + +// static +bool XPCConvert::JSObject2NativeInterface(JSContext* cx, void** dest, + HandleObject src, const nsID* iid, + nsISupports* aOuter, nsresult* pErr) { + MOZ_ASSERT(dest, "bad param"); + MOZ_ASSERT(src, "bad param"); + MOZ_ASSERT(iid, "bad param"); + + js::AssertSameCompartment(cx, src); + + *dest = nullptr; + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + } + + nsISupports* iface; + + if (!aOuter) { + // Note that if we have a non-null aOuter then it means that we are + // forcing the creation of a wrapper even if the object *is* a + // wrappedNative or other wise has 'nsISupportness'. + // This allows wrapJSAggregatedToNative to work. + + // If we're looking at a security wrapper, see now if we're allowed to + // pass it to C++. If we are, then fall through to the code below. If + // we aren't, throw an exception eagerly. + // + // NB: It's very important that we _don't_ unwrap in the aOuter case, + // because the caller may explicitly want to create the XPCWrappedJS + // around a security wrapper. XBL does this with Xrays from the XBL + // scope - see nsBindingManager::GetBindingImplementation. + // + // It's also very important that "inner" be rooted here. + RootedObject inner( + cx, js::CheckedUnwrapDynamic(src, cx, + /* stopAtWindowProxy = */ false)); + if (!inner) { + if (pErr) { + *pErr = NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + return false; + } + + // Is this really a native xpcom object with a wrapper? + XPCWrappedNative* wrappedNative = nullptr; + if (IsWrappedNativeReflector(inner)) { + wrappedNative = XPCWrappedNative::Get(inner); + } + if (wrappedNative) { + iface = wrappedNative->GetIdentityObject(); + return NS_SUCCEEDED(iface->QueryInterface(*iid, dest)); + } + // else... + + // Deal with slim wrappers here. + if (GetISupportsFromJSObject(inner ? inner : src, &iface)) { + if (iface && NS_SUCCEEDED(iface->QueryInterface(*iid, dest))) { + return true; + } + + // If that failed, and iid is for mozIDOMWindowProxy, we actually + // want the outer! + if (iid->Equals(NS_GET_IID(mozIDOMWindowProxy))) { + if (nsCOMPtr<mozIDOMWindow> inner = do_QueryInterface(iface)) { + iface = nsPIDOMWindowInner::From(inner)->GetOuterWindow(); + return NS_SUCCEEDED(iface->QueryInterface(*iid, dest)); + } + } + + return false; + } + } + + RefPtr<nsXPCWrappedJS> wrapper; + nsresult rv = + nsXPCWrappedJS::GetNewOrUsed(cx, src, *iid, getter_AddRefs(wrapper)); + if (pErr) { + *pErr = rv; + } + + if (NS_FAILED(rv) || !wrapper) { + return false; + } + + // If the caller wanted to aggregate this JS object to a native, + // attach it to the wrapper. Note that we allow a maximum of one + // aggregated native for a given XPCWrappedJS. + if (aOuter) { + wrapper->SetAggregatedNativeObject(aOuter); + } + + // We need to go through the QueryInterface logic to make this return + // the right thing for the various 'special' interfaces; e.g. + // nsISimpleEnumerator. We must use AggregatedQueryInterface in cases where + // there is an outer to avoid nasty recursion. + rv = aOuter ? wrapper->AggregatedQueryInterface(*iid, dest) + : wrapper->QueryInterface(*iid, dest); + if (pErr) { + *pErr = rv; + } + return NS_SUCCEEDED(rv); +} + +/***************************************************************************/ +/***************************************************************************/ + +// static +nsresult XPCConvert::ConstructException(nsresult rv, const char* message, + const char* ifaceName, + const char* methodName, + nsISupports* data, Exception** exceptn, + JSContext* cx, Value* jsExceptionPtr) { + MOZ_ASSERT(!cx == !jsExceptionPtr, + "Expected cx and jsExceptionPtr to cooccur."); + + static const char format[] = "\'%s\' when calling method: [%s::%s]"; + const char* msg = message; + nsAutoCString sxmsg; // must have the same lifetime as msg + + nsCOMPtr<nsIScriptError> errorObject = do_QueryInterface(data); + if (errorObject) { + nsString xmsg; + if (NS_SUCCEEDED(errorObject->GetMessageMoz(xmsg))) { + CopyUTF16toUTF8(xmsg, sxmsg); + msg = sxmsg.get(); + } + } + if (!msg) { + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &msg) || !msg) { + msg = "<error>"; + } + } + + nsCString msgStr(msg); + if (ifaceName && methodName) { + msgStr.AppendPrintf(format, msg, ifaceName, methodName); + } + + RefPtr<Exception> e = new Exception(msgStr, rv, ""_ns, nullptr, data); + + if (cx && jsExceptionPtr) { + e->StowJSVal(*jsExceptionPtr); + } + + e.forget(exceptn); + return NS_OK; +} + +/********************************/ + +class MOZ_STACK_CLASS AutoExceptionRestorer { + public: + AutoExceptionRestorer(JSContext* cx, const Value& v) + : mContext(cx), tvr(cx, v) { + JS_ClearPendingException(mContext); + } + + ~AutoExceptionRestorer() { JS_SetPendingException(mContext, tvr); } + + private: + JSContext* const mContext; + RootedValue tvr; +}; + +static nsresult JSErrorToXPCException(JSContext* cx, const char* toStringResult, + const char* ifaceName, + const char* methodName, + const JSErrorReport* report, + Exception** exceptn) { + nsresult rv = NS_ERROR_FAILURE; + RefPtr<nsScriptError> data; + if (report) { + nsAutoString bestMessage; + if (report->message()) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(report->message().c_str()), + bestMessage); + } else if (toStringResult) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(toStringResult), bestMessage); + } else { + bestMessage.AssignLiteral("JavaScript Error"); + } + + const char16_t* linebuf = report->linebuf(); + uint32_t flags = report->isWarning() ? nsIScriptError::warningFlag + : nsIScriptError::errorFlag; + + data = new nsScriptError(); + data->nsIScriptError::InitWithWindowID( + bestMessage, NS_ConvertUTF8toUTF16(report->filename), + linebuf ? nsDependentString(linebuf, report->linebufLength()) + : EmptyString(), + report->lineno, report->tokenOffset(), flags, "XPConnect JavaScript"_ns, + nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx)); + } + + if (data) { + // Pass nullptr for the message: ConstructException will get a message + // from the nsIScriptError. + rv = XPCConvert::ConstructException( + NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, nullptr, ifaceName, + methodName, static_cast<nsIScriptError*>(data.get()), exceptn, nullptr, + nullptr); + } else { + rv = XPCConvert::ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR, nullptr, + ifaceName, methodName, nullptr, exceptn, + nullptr, nullptr); + } + return rv; +} + +// static +nsresult XPCConvert::JSValToXPCException(JSContext* cx, MutableHandleValue s, + const char* ifaceName, + const char* methodName, + Exception** exceptn) { + AutoExceptionRestorer aer(cx, s); + + if (!s.isPrimitive()) { + // we have a JSObject + RootedObject obj(cx, s.toObjectOrNull()); + + if (!obj) { + NS_ERROR("when is an object not an object?"); + return NS_ERROR_FAILURE; + } + + // is this really a native xpcom object with a wrapper? + JSObject* unwrapped = + js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false); + if (!unwrapped) { + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + // It's OK to use ReflectorToISupportsStatic, because we have already + // stripped off wrappers. + if (nsCOMPtr<nsISupports> supports = + ReflectorToISupportsStatic(unwrapped)) { + nsCOMPtr<Exception> iface = do_QueryInterface(supports); + if (iface) { + // just pass through the exception (with extra ref and all) + iface.forget(exceptn); + return NS_OK; + } + + // it is a wrapped native, but not an exception! + return ConstructException(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT, nullptr, + ifaceName, methodName, supports, exceptn, + nullptr, nullptr); + } else { + // It is a JSObject, but not a wrapped native... + + // If it is an engine Error with an error report then let's + // extract the report and build an xpcexception from that + const JSErrorReport* report; + if (nullptr != (report = JS_ErrorFromException(cx, obj))) { + JS::UniqueChars toStringResult; + RootedString str(cx, ToString(cx, s)); + if (str) { + toStringResult = JS_EncodeStringToUTF8(cx, str); + } + return JSErrorToXPCException(cx, toStringResult.get(), ifaceName, + methodName, report, exceptn); + } + + // XXX we should do a check against 'js_ErrorClass' here and + // do the right thing - even though it has no JSErrorReport, + // The fact that it is a JSError exceptions means we can extract + // particular info and our 'result' should reflect that. + + // otherwise we'll just try to convert it to a string + + JSString* str = ToString(cx, s); + if (!str) { + return NS_ERROR_FAILURE; + } + + JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str); + if (!strBytes) { + return NS_ERROR_FAILURE; + } + + return ConstructException(NS_ERROR_XPC_JS_THREW_JS_OBJECT, strBytes.get(), + ifaceName, methodName, nullptr, exceptn, cx, + s.address()); + } + } + + if (s.isUndefined() || s.isNull()) { + return ConstructException(NS_ERROR_XPC_JS_THREW_NULL, nullptr, ifaceName, + methodName, nullptr, exceptn, cx, s.address()); + } + + if (s.isNumber()) { + // lets see if it looks like an nsresult + nsresult rv; + double number; + bool isResult = false; + + if (s.isInt32()) { + rv = (nsresult)s.toInt32(); + if (NS_FAILED(rv)) { + isResult = true; + } else { + number = (double)s.toInt32(); + } + } else { + number = s.toDouble(); + if (number > 0.0 && number < (double)0xffffffff && + 0.0 == fmod(number, 1)) { + // Visual Studio 9 doesn't allow casting directly from a + // double to an enumeration type, contrary to 5.2.9(10) of + // C++11, so add an intermediate cast. + rv = (nsresult)(uint32_t)number; + if (NS_FAILED(rv)) { + isResult = true; + } + } + } + + if (isResult) { + return ConstructException(rv, nullptr, ifaceName, methodName, nullptr, + exceptn, cx, s.address()); + } else { + // XXX all this nsISupportsDouble code seems a little redundant + // now that we're storing the Value in the exception... + nsCOMPtr<nsISupportsDouble> data; + nsCOMPtr<nsIComponentManager> cm; + if (NS_FAILED(NS_GetComponentManager(getter_AddRefs(cm))) || !cm || + NS_FAILED(cm->CreateInstanceByContractID( + NS_SUPPORTS_DOUBLE_CONTRACTID, NS_GET_IID(nsISupportsDouble), + getter_AddRefs(data)))) { + return NS_ERROR_FAILURE; + } + data->SetData(number); + rv = ConstructException(NS_ERROR_XPC_JS_THREW_NUMBER, nullptr, ifaceName, + methodName, data, exceptn, cx, s.address()); + return rv; + } + } + + // otherwise we'll just try to convert it to a string + // Note: e.g., bools get converted to JSStrings by this code. + + JSString* str = ToString(cx, s); + if (str) { + if (JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str)) { + return ConstructException(NS_ERROR_XPC_JS_THREW_STRING, strBytes.get(), + ifaceName, methodName, nullptr, exceptn, cx, + s.address()); + } + } + return NS_ERROR_FAILURE; +} + +/***************************************************************************/ + +// array fun... + +// static +bool XPCConvert::NativeArray2JS(JSContext* cx, MutableHandleValue d, + const void* buf, const nsXPTType& type, + const nsID* iid, uint32_t count, + nsresult* pErr) { + MOZ_ASSERT(buf || count == 0, "Must have buf or 0 elements"); + + RootedObject array(cx, JS::NewArrayObject(cx, count)); + if (!array) { + return false; + } + + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + } + + RootedValue current(cx, JS::NullValue()); + for (uint32_t i = 0; i < count; ++i) { + if (!NativeData2JS(cx, ¤t, type.ElementPtr(buf, i), type, iid, 0, + pErr) || + !JS_DefineElement(cx, array, i, current, JSPROP_ENUMERATE)) + return false; + } + + if (pErr) { + *pErr = NS_OK; + } + d.setObject(*array); + return true; +} + +// static +bool XPCConvert::JSArray2Native(JSContext* cx, JS::HandleValue aJSVal, + const nsXPTType& aEltType, const nsIID* aIID, + nsresult* pErr, + const ArrayAllocFixupLen& aAllocFixupLen) { + // Wrap aAllocFixupLen to check length is within bounds & initialize the + // allocated memory if needed. + auto allocFixupLen = [&](uint32_t* aLength) -> void* { + if (*aLength > (UINT32_MAX / aEltType.Stride())) { + return nullptr; // Byte length doesn't fit in uint32_t + } + + void* buf = aAllocFixupLen(aLength); + + // Ensure the buffer has valid values for each element. We can skip this + // for arithmetic types, as they do not require initialization. + if (buf && !aEltType.IsArithmetic()) { + for (uint32_t i = 0; i < *aLength; ++i) { + InitializeValue(aEltType, aEltType.ElementPtr(buf, i)); + } + } + return buf; + }; + + // JSArray2Native only accepts objects (Array and TypedArray). + if (!aJSVal.isObject()) { + if (pErr) { + *pErr = NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY; + } + return false; + } + RootedObject jsarray(cx, &aJSVal.toObject()); + + if (pErr) { + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + } + + if (JS_IsTypedArrayObject(jsarray)) { + // Fast conversion of typed arrays to native using memcpy. No float or + // double canonicalization is done. ArrayBuffers are not accepted; + // create a properly typed array view on them first. The element type of + // array must match the XPCOM type in size, type and signedness exactly. + // As an exception, Uint8ClampedArray is allowed for arrays of uint8_t. + // DataViews are not supported. + + nsXPTTypeTag tag; + switch (JS_GetArrayBufferViewType(jsarray)) { + case js::Scalar::Int8: + tag = TD_INT8; + break; + case js::Scalar::Uint8: + tag = TD_UINT8; + break; + case js::Scalar::Uint8Clamped: + tag = TD_UINT8; + break; + case js::Scalar::Int16: + tag = TD_INT16; + break; + case js::Scalar::Uint16: + tag = TD_UINT16; + break; + case js::Scalar::Int32: + tag = TD_INT32; + break; + case js::Scalar::Uint32: + tag = TD_UINT32; + break; + case js::Scalar::Float32: + tag = TD_FLOAT; + break; + case js::Scalar::Float64: + tag = TD_DOUBLE; + break; + default: + return false; + } + if (aEltType.Tag() != tag) { + return false; + } + + // Allocate the backing buffer before getting the view data in case + // allocFixupLen can cause GCs. + uint32_t length; + { + // nsTArray and code below uses uint32_t lengths, so reject large typed + // arrays. + size_t fullLength = JS_GetTypedArrayLength(jsarray); + if (fullLength > UINT32_MAX) { + return false; + } + length = uint32_t(fullLength); + } + void* buf = allocFixupLen(&length); + if (!buf) { + return false; + } + + // Get the backing memory buffer to copy out of. + JS::AutoCheckCannotGC nogc; + bool isShared = false; + const void* data = JS_GetArrayBufferViewData(jsarray, &isShared, nogc); + + // Require opting in to shared memory - a future project. + if (isShared) { + return false; + } + + // Directly copy data into the allocated target buffer. + memcpy(buf, data, length * aEltType.Stride()); + return true; + } + + // If jsarray is not a TypedArrayObject, check for an Array object. + uint32_t length = 0; + bool isArray = false; + if (!JS::IsArrayObject(cx, jsarray, &isArray) || !isArray || + !JS::GetArrayLength(cx, jsarray, &length)) { + if (pErr) { + *pErr = NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY; + } + return false; + } + + void* buf = allocFixupLen(&length); + if (!buf) { + return false; + } + + // Translate each array element separately. + RootedValue current(cx); + for (uint32_t i = 0; i < length; ++i) { + if (!JS_GetElement(cx, jsarray, i, ¤t) || + !JSData2Native(cx, aEltType.ElementPtr(buf, i), current, aEltType, aIID, + 0, pErr)) { + // Array element conversion failed. Clean up all elements converted + // before the error. Caller handles freeing 'buf'. + for (uint32_t j = 0; j < i; ++j) { + DestructValue(aEltType, aEltType.ElementPtr(buf, j)); + } + return false; + } + } + + return true; +} + +/***************************************************************************/ + +// Internal implementation details for xpc::CleanupValue. + +void xpc::InnerCleanupValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen) { + MOZ_ASSERT(!aType.IsArithmetic(), + "Arithmetic types should not get to InnerCleanupValue!"); + MOZ_ASSERT(aArrayLen == 0 || aType.Tag() == nsXPTType::T_PSTRING_SIZE_IS || + aType.Tag() == nsXPTType::T_PWSTRING_SIZE_IS || + aType.Tag() == nsXPTType::T_LEGACY_ARRAY, + "Array lengths may only appear for certain types!"); + + switch (aType.Tag()) { + // Pointer types + case nsXPTType::T_DOMOBJECT: + aType.GetDOMObjectInfo().Cleanup(*(void**)aValue); + break; + + case nsXPTType::T_PROMISE: + (*(mozilla::dom::Promise**)aValue)->Release(); + break; + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + (*(nsISupports**)aValue)->Release(); + break; + + // String types + case nsXPTType::T_ASTRING: + ((nsAString*)aValue)->Truncate(); + break; + case nsXPTType::T_UTF8STRING: + case nsXPTType::T_CSTRING: + ((nsACString*)aValue)->Truncate(); + break; + + // Pointer Types + case nsXPTType::T_NSIDPTR: + case nsXPTType::T_CHAR_STR: + case nsXPTType::T_WCHAR_STR: + case nsXPTType::T_PSTRING_SIZE_IS: + case nsXPTType::T_PWSTRING_SIZE_IS: + free(*(void**)aValue); + break; + + // Legacy Array Type + case nsXPTType::T_LEGACY_ARRAY: { + const nsXPTType& elty = aType.ArrayElementType(); + void* elements = *(void**)aValue; + + for (uint32_t i = 0; i < aArrayLen; ++i) { + DestructValue(elty, elty.ElementPtr(elements, i)); + } + free(elements); + break; + } + + // Array Type + case nsXPTType::T_ARRAY: { + const nsXPTType& elty = aType.ArrayElementType(); + auto* array = (xpt::detail::UntypedTArray*)aValue; + + for (uint32_t i = 0; i < array->Length(); ++i) { + DestructValue(elty, elty.ElementPtr(array->Elements(), i)); + } + array->Clear(); + break; + } + + // Clear nsID& parameters to `0` + case nsXPTType::T_NSID: + ((nsID*)aValue)->Clear(); + break; + + // Clear the JS::Value to `undefined` + case nsXPTType::T_JSVAL: + ((JS::Value*)aValue)->setUndefined(); + break; + + // Non-arithmetic types requiring no cleanup + case nsXPTType::T_VOID: + break; + + default: + MOZ_CRASH("Unknown Type!"); + } + + // Clear any non-complex values to the valid '0' state. + if (!aType.IsComplex()) { + aType.ZeroValue(aValue); + } +} + +/***************************************************************************/ + +// Implementation of xpc::InitializeValue. + +void xpc::InitializeValue(const nsXPTType& aType, void* aValue) { + switch (aType.Tag()) { + // Use placement-new to initialize complex values +#define XPT_INIT_TYPE(tag, type) \ + case tag: \ + new (aValue) type(); \ + break; + XPT_FOR_EACH_COMPLEX_TYPE(XPT_INIT_TYPE) +#undef XPT_INIT_TYPE + + // The remaining types have valid states where all bytes are '0'. + default: + aType.ZeroValue(aValue); + break; + } +} + +// In XPT_FOR_EACH_COMPLEX_TYPE, typenames may be namespaced (such as +// xpt::UntypedTArray). Namespaced typenames cannot be used to explicitly invoke +// destructors, so this method acts as a helper to let us call the destructor of +// these objects. +template <typename T> +static void _DestructValueHelper(void* aValue) { + static_cast<T*>(aValue)->~T(); +} + +void xpc::DestructValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen) { + // Get aValue into an clean, empty state. + xpc::CleanupValue(aType, aValue, aArrayLen); + + // Run destructors on complex types. + switch (aType.Tag()) { +#define XPT_RUN_DESTRUCTOR(tag, type) \ + case tag: \ + _DestructValueHelper<type>(aValue); \ + break; + XPT_FOR_EACH_COMPLEX_TYPE(XPT_RUN_DESTRUCTOR) +#undef XPT_RUN_DESTRUCTOR + default: + break; // dtor is a no-op on other types. + } +} diff --git a/js/xpconnect/src/XPCDebug.cpp b/js/xpconnect/src/XPCDebug.cpp new file mode 100644 index 0000000000..25cf8758b2 --- /dev/null +++ b/js/xpconnect/src/XPCDebug.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "js/friend/DumpFunctions.h" // JS::FormatStackDump +#include "nsThreadUtils.h" +#include "nsContentUtils.h" + +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +# include <windows.h> +# include "nsPrintfCString.h" +#endif + +#ifdef ANDROID +# include <android/log.h> +#endif + +static void DebugDump(const char* str) { +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsPrintfCString output("%s\n", str); + OutputDebugStringA(output.get()); + } +#elif defined(ANDROID) + __android_log_print(ANDROID_LOG_DEBUG, "Gecko", "%s\n", str); +#endif + printf("%s\n", str); +} + +bool xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps) { + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (!cx) { + printf("there is no JSContext on the stack!\n"); + } else if (JS::UniqueChars buf = + xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps)) { + DebugDump(buf.get()); + } + return true; +} + +JS::UniqueChars xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals, + bool showThisProps) { + JS::AutoSaveExceptionState state(cx); + + JS::UniqueChars buf = + JS::FormatStackDump(cx, showArgs, showLocals, showThisProps); + if (!buf) { + DebugDump("Failed to format JavaScript stack for dump"); + } + + state.restore(); + return buf; +} diff --git a/js/xpconnect/src/XPCException.cpp b/js/xpconnect/src/XPCException.cpp new file mode 100644 index 0000000000..64a83e3b31 --- /dev/null +++ b/js/xpconnect/src/XPCException.cpp @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +/* An implementaion of nsIException. */ + +#include "xpcprivate.h" +#include "nsError.h" + +#include <iterator> + +/***************************************************************************/ +/* Quick and dirty mapping of well known result codes to strings. We only + * call this when building an exception object, so iterating the short array + * is not too bad. + * + * It sure would be nice to have exceptions declared in idl and available + * in some more global way at runtime. + */ + +static const struct ResultMap { + nsresult rv; + const char* name; + const char* format; +} map[] = { +#define XPC_MSG_DEF(val, format) {(val), #val, format}, +#include "xpc.msg" +#undef XPC_MSG_DEF + {NS_OK, 0, 0} // sentinel to mark end of array +}; + +#define RESULT_COUNT (std::size(map) - 1) + +// static +bool nsXPCException::NameAndFormatForNSResult(nsresult rv, const char** name, + const char** format) { + for (const ResultMap* p = map; p->name; p++) { + if (rv == p->rv) { + if (name) *name = p->name; + if (format) *format = p->format; + return true; + } + } + return false; +} + +// static +const void* nsXPCException::IterateNSResults(nsresult* rv, const char** name, + const char** format, + const void** iterp) { + const ResultMap* p = (const ResultMap*)*iterp; + if (!p) { + p = map; + } else { + p++; + } + if (!p->name) { + p = nullptr; + } else { + if (rv) { + *rv = p->rv; + } + if (name) { + *name = p->name; + } + if (format) { + *format = p->format; + } + } + *iterp = p; + return p; +} + +// static +uint32_t nsXPCException::GetNSResultCount() { return RESULT_COUNT; } diff --git a/js/xpconnect/src/XPCForwards.h b/js/xpconnect/src/XPCForwards.h new file mode 100644 index 0000000000..56ad984025 --- /dev/null +++ b/js/xpconnect/src/XPCForwards.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +/* Private forward declarations. */ + +#ifndef xpcforwards_h___ +#define xpcforwards_h___ + +// forward declarations of internally used classes... + +class nsXPConnect; +class XPCJSContext; +class XPCJSRuntime; +class XPCContext; +class XPCCallContext; + +class XPCJSThrower; + +class nsXPCWrappedJS; + +class XPCNativeMember; +class XPCNativeInterface; +class XPCNativeSet; + +class XPCWrappedNative; +class XPCWrappedNativeProto; +class XPCWrappedNativeTearOff; + +class JSObject2WrappedJSMap; +class Native2WrappedNativeMap; +class IID2NativeInterfaceMap; +class ClassInfo2NativeSetMap; +class ClassInfo2WrappedNativeProtoMap; +class NativeSetMap; +class JSObject2JSObjectMap; + +class nsXPCComponents; +class nsXPCComponents_Interfaces; +class nsXPCComponents_Classes; +class nsXPCComponents_Results; +class nsXPCComponents_ID; +class nsXPCComponents_Exception; +class nsXPCComponents_Constructor; +class nsXPCComponents_Utils; + +class AutoMarkingPtr; + +#endif /* xpcforwards_h___ */ diff --git a/js/xpconnect/src/XPCInlines.h b/js/xpconnect/src/XPCInlines.h new file mode 100644 index 0000000000..bc29c23ff8 --- /dev/null +++ b/js/xpconnect/src/XPCInlines.h @@ -0,0 +1,367 @@ +/* -*- 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/. */ + +/* private inline methods (#include'd by xpcprivate.h). */ + +#ifndef xpcinlines_h___ +#define xpcinlines_h___ + +#include <algorithm> + +#include "js/PropertyAndElement.h" // JS_HasProperty, JS_HasPropertyById + +/***************************************************************************/ + +inline void XPCJSRuntime::AddSubjectToFinalizationWJS( + nsXPCWrappedJS* wrappedJS) { + mSubjectToFinalizationWJS.insertBack(wrappedJS); +} + +/***************************************************************************/ + +inline bool XPCCallContext::IsValid() const { return mState != INIT_FAILED; } + +inline XPCJSContext* XPCCallContext::GetContext() const { + CHECK_STATE(HAVE_CONTEXT); + return mXPCJSContext; +} + +inline JSContext* XPCCallContext::GetJSContext() const { + CHECK_STATE(HAVE_CONTEXT); + return mJSContext; +} + +inline XPCCallContext* XPCCallContext::GetPrevCallContext() const { + CHECK_STATE(HAVE_CONTEXT); + return mPrevCallContext; +} + +inline XPCWrappedNative* XPCCallContext::GetWrapper() const { + if (mState == INIT_FAILED) { + return nullptr; + } + + CHECK_STATE(HAVE_OBJECT); + return mWrapper; +} + +inline bool XPCCallContext::CanGetTearOff() const { + return mState >= HAVE_OBJECT; +} + +inline XPCWrappedNativeTearOff* XPCCallContext::GetTearOff() const { + CHECK_STATE(HAVE_OBJECT); + return mTearOff; +} + +inline nsIXPCScriptable* XPCCallContext::GetScriptable() const { + CHECK_STATE(HAVE_OBJECT); + return mScriptable; +} + +inline XPCNativeSet* XPCCallContext::GetSet() const { + CHECK_STATE(HAVE_NAME); + return mSet; +} + +inline XPCNativeInterface* XPCCallContext::GetInterface() const { + CHECK_STATE(HAVE_NAME); + return mInterface; +} + +inline XPCNativeMember* XPCCallContext::GetMember() const { + CHECK_STATE(HAVE_NAME); + return mMember; +} + +inline bool XPCCallContext::HasInterfaceAndMember() const { + return mState >= HAVE_NAME && mInterface && mMember; +} + +inline bool XPCCallContext::GetStaticMemberIsLocal() const { + CHECK_STATE(HAVE_NAME); + return mStaticMemberIsLocal; +} + +inline unsigned XPCCallContext::GetArgc() const { + CHECK_STATE(READY_TO_CALL); + return mArgc; +} + +inline JS::Value* XPCCallContext::GetArgv() const { + CHECK_STATE(READY_TO_CALL); + return mArgv; +} + +inline void XPCCallContext::SetRetVal(const JS::Value& val) { + CHECK_STATE(HAVE_ARGS); + if (mRetVal) { + *mRetVal = val; + } +} + +inline jsid XPCCallContext::GetResolveName() const { + CHECK_STATE(HAVE_CONTEXT); + return GetContext()->GetResolveName(); +} + +inline jsid XPCCallContext::SetResolveName(JS::HandleId name) { + CHECK_STATE(HAVE_CONTEXT); + return GetContext()->SetResolveName(name); +} + +inline XPCWrappedNative* XPCCallContext::GetResolvingWrapper() const { + CHECK_STATE(HAVE_OBJECT); + return GetContext()->GetResolvingWrapper(); +} + +inline XPCWrappedNative* XPCCallContext::SetResolvingWrapper( + XPCWrappedNative* w) { + CHECK_STATE(HAVE_OBJECT); + return GetContext()->SetResolvingWrapper(w); +} + +inline uint16_t XPCCallContext::GetMethodIndex() const { + CHECK_STATE(HAVE_OBJECT); + return mMethodIndex; +} + +/***************************************************************************/ +inline XPCNativeInterface* XPCNativeMember::GetInterface() const { + XPCNativeMember* arrayStart = + const_cast<XPCNativeMember*>(this - mIndexInInterface); + size_t arrayStartOffset = XPCNativeInterface::OffsetOfMembers(); + char* xpcNativeInterfaceStart = + reinterpret_cast<char*>(arrayStart) - arrayStartOffset; + return reinterpret_cast<XPCNativeInterface*>(xpcNativeInterfaceStart); +} + +/***************************************************************************/ + +inline const nsIID* XPCNativeInterface::GetIID() const { return &mInfo->IID(); } + +inline const char* XPCNativeInterface::GetNameString() const { + return mInfo->Name(); +} + +inline XPCNativeMember* XPCNativeInterface::FindMember(jsid name) const { + const XPCNativeMember* member = mMembers; + for (int i = (int)mMemberCount; i > 0; i--, member++) { + if (member->GetName() == name) { + return const_cast<XPCNativeMember*>(member); + } + } + return nullptr; +} + +/* static */ +inline size_t XPCNativeInterface::OffsetOfMembers() { + return offsetof(XPCNativeInterface, mMembers); +} + +/***************************************************************************/ + +inline XPCNativeSetKey::XPCNativeSetKey(XPCNativeSet* baseSet, + XPCNativeInterface* addition) + : mCx(nullptr), mBaseSet(baseSet), mAddition(addition) { + MOZ_ASSERT(mBaseSet); + MOZ_ASSERT(mAddition); + MOZ_ASSERT(!mBaseSet->HasInterface(mAddition)); +} + +/***************************************************************************/ + +inline bool XPCNativeSet::FindMember(jsid name, XPCNativeMember** pMember, + uint16_t* pInterfaceIndex) const { + XPCNativeInterface* const* iface; + int count = (int)mInterfaceCount; + int i; + + // look for interface names first + + for (i = 0, iface = mInterfaces; i < count; i++, iface++) { + if (name == (*iface)->GetName()) { + if (pMember) { + *pMember = nullptr; + } + if (pInterfaceIndex) { + *pInterfaceIndex = (uint16_t)i; + } + return true; + } + } + + // look for method names + for (i = 0, iface = mInterfaces; i < count; i++, iface++) { + XPCNativeMember* member = (*iface)->FindMember(name); + if (member) { + if (pMember) { + *pMember = member; + } + if (pInterfaceIndex) { + *pInterfaceIndex = (uint16_t)i; + } + return true; + } + } + return false; +} + +inline bool XPCNativeSet::FindMember( + jsid name, XPCNativeMember** pMember, + RefPtr<XPCNativeInterface>* pInterface) const { + uint16_t index; + if (!FindMember(name, pMember, &index)) { + return false; + } + *pInterface = mInterfaces[index]; + return true; +} + +inline bool XPCNativeSet::FindMember(JS::HandleId name, + XPCNativeMember** pMember, + RefPtr<XPCNativeInterface>* pInterface, + XPCNativeSet* protoSet, + bool* pIsLocal) const { + XPCNativeMember* Member; + RefPtr<XPCNativeInterface> Interface; + XPCNativeMember* protoMember; + + if (!FindMember(name, &Member, &Interface)) { + return false; + } + + *pMember = Member; + + *pIsLocal = !Member || !protoSet || + (protoSet != this && + !protoSet->MatchesSetUpToInterface(this, Interface) && + (!protoSet->FindMember(name, &protoMember, (uint16_t*)nullptr) || + protoMember != Member)); + + *pInterface = std::move(Interface); + + return true; +} + +inline bool XPCNativeSet::HasInterface(XPCNativeInterface* aInterface) const { + XPCNativeInterface* const* pp = mInterfaces; + + for (int i = (int)mInterfaceCount; i > 0; i--, pp++) { + if (aInterface == *pp) { + return true; + } + } + return false; +} + +inline bool XPCNativeSet::MatchesSetUpToInterface( + const XPCNativeSet* other, XPCNativeInterface* iface) const { + int count = std::min(int(mInterfaceCount), int(other->mInterfaceCount)); + + XPCNativeInterface* const* pp1 = mInterfaces; + XPCNativeInterface* const* pp2 = other->mInterfaces; + + for (int i = (int)count; i > 0; i--, pp1++, pp2++) { + XPCNativeInterface* cur = (*pp1); + if (cur != (*pp2)) { + return false; + } + if (cur == iface) { + return true; + } + } + return false; +} + +/***************************************************************************/ + +inline JSObject* XPCWrappedNativeTearOff::GetJSObjectPreserveColor() const { + return mJSObject.unbarrieredGetPtr(); +} + +inline JSObject* XPCWrappedNativeTearOff::GetJSObject() { return mJSObject; } + +inline void XPCWrappedNativeTearOff::SetJSObject(JSObject* JSObj) { + MOZ_ASSERT(!IsMarked()); + mJSObject = JSObj; +} + +inline void XPCWrappedNativeTearOff::JSObjectMoved(JSObject* obj, + const JSObject* old) { + MOZ_ASSERT(!IsMarked()); + MOZ_ASSERT(mJSObject == old); + mJSObject = obj; +} + +inline XPCWrappedNativeTearOff::~XPCWrappedNativeTearOff() { + MOZ_COUNT_DTOR(XPCWrappedNativeTearOff); + MOZ_ASSERT(!(GetInterface() || GetNative() || GetJSObjectPreserveColor()), + "tearoff not empty in dtor"); +} + +/***************************************************************************/ + +inline void XPCWrappedNative::SweepTearOffs() { + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; + to = to->GetNextTearOff()) { + bool marked = to->IsMarked(); + to->Unmark(); + if (marked) { + continue; + } + + // If this tearoff does not have a live dedicated JSObject, + // then let's recycle it. + if (!to->GetJSObjectPreserveColor()) { + to->SetNative(nullptr); + to->SetInterface(nullptr); + } + } +} + +/***************************************************************************/ + +inline bool xpc_ForcePropertyResolve(JSContext* cx, JS::HandleObject obj, + jsid idArg) { + JS::RootedId id(cx, idArg); + bool dummy; + return JS_HasPropertyById(cx, obj, id, &dummy); +} + +inline jsid GetJSIDByIndex(JSContext* cx, unsigned index) { + XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance(); + return xpcrt->GetStringID(index); +} + +inline bool ThrowBadParam(nsresult rv, unsigned paramNum, XPCCallContext& ccx) { + XPCThrower::ThrowBadParam(rv, paramNum, ccx); + return false; +} + +inline void ThrowBadResult(nsresult result, XPCCallContext& ccx) { + XPCThrower::ThrowBadResult(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE, result, ccx); +} + +/***************************************************************************/ + +inline void xpc::CleanupValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen) { + // Check if we can do a cheap early return, and only perform the inner call + // if we can't. We never have to clean up null pointer types or arithmetic + // types. + // + // NOTE: We can skip zeroing arithmetic types in CleanupValue, as they are + // already in a valid state. + if (aType.IsArithmetic() || (aType.IsPointer() && !*(void**)aValue)) { + return; + } + xpc::InnerCleanupValue(aType, aValue, aArrayLen); +} + +/***************************************************************************/ + +#endif /* xpcinlines_h___ */ diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp new file mode 100644 index 0000000000..0f54e00cd5 --- /dev/null +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -0,0 +1,1500 @@ +/* -*- 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/. */ + +/* Per JSContext object */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +#include "xpcprivate.h" +#include "xpcpublic.h" +#include "XPCWrapper.h" +#include "XPCJSMemoryReporter.h" +#include "XPCSelfHostedShmem.h" +#include "WrapperFactory.h" +#include "mozJSModuleLoader.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" + +#include "nsIObserverService.h" +#include "nsIDebug2.h" +#include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Services.h" +#ifdef FUZZING +# include "mozilla/StaticPrefs_fuzzing.h" +#endif +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "nsContentUtils.h" +#include "nsCCUncollectableMarker.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollector.h" +#include "nsJSEnvironment.h" +#include "jsapi.h" +#include "js/ArrayBuffer.h" +#include "js/ContextOptions.h" +#include "js/HelperThreadAPI.h" +#include "js/Initialization.h" +#include "js/MemoryMetrics.h" +#include "js/OffThreadScriptCompilation.h" +#include "js/WasmFeatures.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/extensions/WebExtensionPolicy.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/Sprintf.h" +#include "mozilla/SystemPrincipal.h" +#include "mozilla/TaskController.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "AccessCheck.h" +#include "nsGlobalWindow.h" +#include "nsAboutProtocolUtils.h" + +#include "GeckoProfiler.h" +#include "nsIXULRuntime.h" +#include "nsJSPrincipals.h" +#include "ExpandedPrincipal.h" + +#if defined(XP_LINUX) && !defined(ANDROID) +// For getrlimit and min/max. +# include <algorithm> +# include <sys/resource.h> +#endif + +#ifdef XP_WIN +// For min. +# include <algorithm> +# include <windows.h> +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpc; +using namespace JS; + +// We will clamp to reasonable values if this isn't set. +#if !defined(PTHREAD_STACK_MIN) +# define PTHREAD_STACK_MIN 0 +#endif + +static void WatchdogMain(void* arg); +class Watchdog; +class WatchdogManager; +class MOZ_RAII AutoLockWatchdog final { + Watchdog* const mWatchdog; + + public: + explicit AutoLockWatchdog(Watchdog* aWatchdog); + ~AutoLockWatchdog(); +}; + +class Watchdog { + public: + explicit Watchdog(WatchdogManager* aManager) + : mManager(aManager), + mLock(nullptr), + mWakeup(nullptr), + mThread(nullptr), + mHibernating(false), + mInitialized(false), + mShuttingDown(false), + mMinScriptRunTimeSeconds(1) {} + ~Watchdog() { MOZ_ASSERT(!Initialized()); } + + WatchdogManager* Manager() { return mManager; } + bool Initialized() { return mInitialized; } + bool ShuttingDown() { return mShuttingDown; } + PRLock* GetLock() { return mLock; } + bool Hibernating() { return mHibernating; } + void WakeUp() { + MOZ_ASSERT(Initialized()); + MOZ_ASSERT(Hibernating()); + mHibernating = false; + PR_NotifyCondVar(mWakeup); + } + + // + // Invoked by the main thread only. + // + + void Init() { + MOZ_ASSERT(NS_IsMainThread()); + mLock = PR_NewLock(); + if (!mLock) { + MOZ_CRASH("PR_NewLock failed."); + } + + mWakeup = PR_NewCondVar(mLock); + if (!mWakeup) { + MOZ_CRASH("PR_NewCondVar failed."); + } + + { + // Make sure the debug service is instantiated before we create the + // watchdog thread, since we intentionally try to keep the thread's stack + // segment as small as possible. It isn't always large enough to + // instantiate a new service, and even when it is, we don't want fault in + // extra pages if we can avoid it. + nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1"); + Unused << dbg; + } + + { + AutoLockWatchdog lock(this); + + // The watchdog thread loop is pretty trivial, and should not + // require much stack space to do its job. So only give it 32KiB + // or the platform minimum. On modern Linux libc this might resolve to + // a runtime call. + size_t watchdogStackSize = PTHREAD_STACK_MIN; + watchdogStackSize = std::max<size_t>(32 * 1024, watchdogStackSize); + + // Gecko uses thread private for accounting and has to clean up at thread + // exit. Therefore, even though we don't have a return value from the + // watchdog, we need to join it on shutdown. + mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, watchdogStackSize); + if (!mThread) { + MOZ_CRASH("PR_CreateThread failed!"); + } + + // WatchdogMain acquires the lock and then asserts mInitialized. So + // make sure to set mInitialized before releasing the lock here so + // that it's atomic with the creation of the thread. + mInitialized = true; + } + } + + void Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(Initialized()); + { // Scoped lock. + AutoLockWatchdog lock(this); + + // Signal to the watchdog thread that it's time to shut down. + mShuttingDown = true; + + // Wake up the watchdog, and wait for it to call us back. + PR_NotifyCondVar(mWakeup); + } + + PR_JoinThread(mThread); + + // The thread sets mShuttingDown to false as it exits. + MOZ_ASSERT(!mShuttingDown); + + // Destroy state. + mThread = nullptr; + PR_DestroyCondVar(mWakeup); + mWakeup = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + + // All done. + mInitialized = false; + } + + void SetMinScriptRunTimeSeconds(int32_t seconds) { + // This variable is atomic, and is set from the main thread without + // locking. + MOZ_ASSERT(seconds > 0); + mMinScriptRunTimeSeconds = seconds; + } + + // + // Invoked by the watchdog thread only. + // + + void Hibernate() { + MOZ_ASSERT(!NS_IsMainThread()); + mHibernating = true; + Sleep(PR_INTERVAL_NO_TIMEOUT); + } + void Sleep(PRIntervalTime timeout) { + MOZ_ASSERT(!NS_IsMainThread()); + AUTO_PROFILER_THREAD_SLEEP; + MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS); + } + void Finished() { + MOZ_ASSERT(!NS_IsMainThread()); + mShuttingDown = false; + } + + int32_t MinScriptRunTimeSeconds() { return mMinScriptRunTimeSeconds; } + + private: + WatchdogManager* mManager; + + PRLock* mLock; + PRCondVar* mWakeup; + PRThread* mThread; + bool mHibernating; + bool mInitialized; + bool mShuttingDown; + mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds; +}; + +#define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time" +#define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time" +#define PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT \ + "dom.max_ext_content_script_run_time" + +static const char* gCallbackPrefs[] = { + "dom.use_watchdog", + PREF_MAX_SCRIPT_RUN_TIME_CONTENT, + PREF_MAX_SCRIPT_RUN_TIME_CHROME, + PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT, + nullptr, +}; + +class WatchdogManager { + public: + explicit WatchdogManager() { + // All the timestamps start at zero. + PodArrayZero(mTimestamps); + + // Register ourselves as an observer to get updates on the pref. + Preferences::RegisterCallbacks(PrefsChanged, gCallbackPrefs, this); + } + + virtual ~WatchdogManager() { + // Shutting down the watchdog requires context-switching to the watchdog + // thread, which isn't great to do in a destructor. So we require + // consumers to shut it down manually before releasing it. + MOZ_ASSERT(!mWatchdog); + } + + private: + static void PrefsChanged(const char* aPref, void* aSelf) { + static_cast<WatchdogManager*>(aSelf)->RefreshWatchdog(); + } + + public: + void Shutdown() { + Preferences::UnregisterCallbacks(PrefsChanged, gCallbackPrefs, this); + } + + void RegisterContext(XPCJSContext* aContext) { + MOZ_ASSERT(NS_IsMainThread()); + AutoLockWatchdog lock(mWatchdog.get()); + + if (aContext->mActive == XPCJSContext::CONTEXT_ACTIVE) { + mActiveContexts.insertBack(aContext); + } else { + mInactiveContexts.insertBack(aContext); + } + + // Enable the watchdog, if appropriate. + RefreshWatchdog(); + } + + void UnregisterContext(XPCJSContext* aContext) { + MOZ_ASSERT(NS_IsMainThread()); + AutoLockWatchdog lock(mWatchdog.get()); + + // aContext must be in one of our two lists, simply remove it. + aContext->LinkedListElement<XPCJSContext>::remove(); + +#ifdef DEBUG + // If this was the last context, we should have already shut down + // the watchdog. + if (mActiveContexts.isEmpty() && mInactiveContexts.isEmpty()) { + MOZ_ASSERT(!mWatchdog); + } +#endif + } + + // Context statistics. These live on the watchdog manager, are written + // from the main thread, and are read from the watchdog thread (holding + // the lock in each case). + void RecordContextActivity(XPCJSContext* aContext, bool active) { + // The watchdog reads this state, so acquire the lock before writing it. + MOZ_ASSERT(NS_IsMainThread()); + AutoLockWatchdog lock(mWatchdog.get()); + + // Write state. + aContext->mLastStateChange = PR_Now(); + aContext->mActive = + active ? XPCJSContext::CONTEXT_ACTIVE : XPCJSContext::CONTEXT_INACTIVE; + UpdateContextLists(aContext); + + // The watchdog may be hibernating, waiting for the context to go + // active. Wake it up if necessary. + if (active && mWatchdog && mWatchdog->Hibernating()) { + mWatchdog->WakeUp(); + } + } + + bool IsAnyContextActive() { return !mActiveContexts.isEmpty(); } + PRTime TimeSinceLastActiveContext() { + // Must be called on the watchdog thread with the lock held. + MOZ_ASSERT(!NS_IsMainThread()); + PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); + MOZ_ASSERT(mActiveContexts.isEmpty()); + MOZ_ASSERT(!mInactiveContexts.isEmpty()); + + // We store inactive contexts with the most recently added inactive + // context at the end of the list. + return PR_Now() - mInactiveContexts.getLast()->mLastStateChange; + } + + void RecordTimestamp(WatchdogTimestampCategory aCategory) { + // Must be called on the watchdog thread with the lock held. + MOZ_ASSERT(!NS_IsMainThread()); + PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); + MOZ_ASSERT(aCategory != TimestampContextStateChange, + "Use RecordContextActivity to update this"); + + mTimestamps[aCategory] = PR_Now(); + } + + PRTime GetContextTimestamp(XPCJSContext* aContext, + const AutoLockWatchdog& aProofOfLock) { + return aContext->mLastStateChange; + } + + PRTime GetTimestamp(WatchdogTimestampCategory aCategory, + const AutoLockWatchdog& aProofOfLock) { + MOZ_ASSERT(aCategory != TimestampContextStateChange, + "Use GetContextTimestamp to retrieve this"); + return mTimestamps[aCategory]; + } + + Watchdog* GetWatchdog() { return mWatchdog.get(); } + + void RefreshWatchdog() { + bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true); + if (wantWatchdog != !!mWatchdog) { + if (wantWatchdog) { + StartWatchdog(); + } else { + StopWatchdog(); + } + } + + if (mWatchdog) { + int32_t contentTime = StaticPrefs::dom_max_script_run_time(); + if (contentTime <= 0) { + contentTime = INT32_MAX; + } + int32_t chromeTime = StaticPrefs::dom_max_chrome_script_run_time(); + if (chromeTime <= 0) { + chromeTime = INT32_MAX; + } + int32_t extTime = StaticPrefs::dom_max_ext_content_script_run_time(); + if (extTime <= 0) { + extTime = INT32_MAX; + } + mWatchdog->SetMinScriptRunTimeSeconds( + std::min({contentTime, chromeTime, extTime})); + } + } + + void StartWatchdog() { + MOZ_ASSERT(!mWatchdog); + mWatchdog = mozilla::MakeUnique<Watchdog>(this); + mWatchdog->Init(); + } + + void StopWatchdog() { + MOZ_ASSERT(mWatchdog); + mWatchdog->Shutdown(); + mWatchdog = nullptr; + } + + template <class Callback> + void ForAllActiveContexts(Callback&& aCallback) { + // This function must be called on the watchdog thread with the lock held. + MOZ_ASSERT(!NS_IsMainThread()); + PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock()); + + for (auto* context = mActiveContexts.getFirst(); context; + context = context->LinkedListElement<XPCJSContext>::getNext()) { + if (!aCallback(context)) { + return; + } + } + } + + private: + void UpdateContextLists(XPCJSContext* aContext) { + // Given aContext whose activity state or timestamp has just changed, + // put it back in the proper position in the proper list. + aContext->LinkedListElement<XPCJSContext>::remove(); + auto& list = aContext->mActive == XPCJSContext::CONTEXT_ACTIVE + ? mActiveContexts + : mInactiveContexts; + + // Either the new list is empty or aContext must be more recent than + // the existing last element. + MOZ_ASSERT_IF(!list.isEmpty(), list.getLast()->mLastStateChange < + aContext->mLastStateChange); + list.insertBack(aContext); + } + + LinkedList<XPCJSContext> mActiveContexts; + LinkedList<XPCJSContext> mInactiveContexts; + mozilla::UniquePtr<Watchdog> mWatchdog; + + // We store ContextStateChange on the contexts themselves. + PRTime mTimestamps[kWatchdogTimestampCategoryCount - 1]; +}; + +AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog) { + if (mWatchdog) { + PR_Lock(mWatchdog->GetLock()); + } +} + +AutoLockWatchdog::~AutoLockWatchdog() { + if (mWatchdog) { + PR_Unlock(mWatchdog->GetLock()); + } +} + +static void WatchdogMain(void* arg) { + AUTO_PROFILER_REGISTER_THREAD("JS Watchdog"); + // Create an nsThread wrapper for the thread and register it with the thread + // manager. + Unused << NS_GetCurrentThread(); + NS_SetCurrentThreadName("JS Watchdog"); + + Watchdog* self = static_cast<Watchdog*>(arg); + WatchdogManager* manager = self->Manager(); + + // Lock lasts until we return + AutoLockWatchdog lock(self); + + MOZ_ASSERT(self->Initialized()); + while (!self->ShuttingDown()) { + // Sleep only 1 second if recently (or currently) active; otherwise, + // hibernate + if (manager->IsAnyContextActive() || + manager->TimeSinceLastActiveContext() <= PRTime(2 * PR_USEC_PER_SEC)) { + self->Sleep(PR_TicksPerSecond()); + } else { + manager->RecordTimestamp(TimestampWatchdogHibernateStart); + self->Hibernate(); + manager->RecordTimestamp(TimestampWatchdogHibernateStop); + } + + // Rise and shine. + manager->RecordTimestamp(TimestampWatchdogWakeup); + + // Don't request an interrupt callback unless the current script has + // been running long enough that we might show the slow script dialog. + // Triggering the callback from off the main thread can be expensive. + + // We want to avoid showing the slow script dialog if the user's laptop + // goes to sleep in the middle of running a script. To ensure this, we + // invoke the interrupt callback after only half the timeout has + // elapsed. The callback simply records the fact that it was called in + // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2) + // seconds and invoke the callback again. This time around it sees + // mSlowScriptSecondHalf is set and so it shows the slow script + // dialog. If the computer is put to sleep during one of the (timeout/2) + // periods, the script still has the other (timeout/2) seconds to + // finish. + if (!self->ShuttingDown() && manager->IsAnyContextActive()) { + bool debuggerAttached = false; + nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1"); + if (dbg) { + dbg->GetIsDebuggerAttached(&debuggerAttached); + } + if (debuggerAttached) { + // We won't be interrupting these scripts anyway. + continue; + } + + PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2; + manager->ForAllActiveContexts([usecs, manager, + &lock](XPCJSContext* aContext) -> bool { + auto timediff = PR_Now() - manager->GetContextTimestamp(aContext, lock); + if (timediff > usecs) { + JS_RequestInterruptCallback(aContext->Context()); + return true; + } + return false; + }); + } + } + + // Tell the manager that we've shut down. + self->Finished(); +} + +PRTime XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) { + AutoLockWatchdog lock(mWatchdogManager->GetWatchdog()); + return aCategory == TimestampContextStateChange + ? mWatchdogManager->GetContextTimestamp(this, lock) + : mWatchdogManager->GetTimestamp(aCategory, lock); +} + +// static +bool XPCJSContext::RecordScriptActivity(bool aActive) { + MOZ_ASSERT(NS_IsMainThread()); + + XPCJSContext* xpccx = XPCJSContext::Get(); + if (!xpccx) { + // mozilla::SpinEventLoopUntil may use AutoScriptActivity(false) after + // we destroyed the XPCJSContext. + MOZ_ASSERT(!aActive); + return false; + } + + bool oldValue = xpccx->SetHasScriptActivity(aActive); + if (aActive == oldValue) { + // Nothing to do. + return oldValue; + } + + if (!aActive) { + ProcessHangMonitor::ClearHang(); + } + xpccx->mWatchdogManager->RecordContextActivity(xpccx, aActive); + + return oldValue; +} + +AutoScriptActivity::AutoScriptActivity(bool aActive) + : mActive(aActive), + mOldValue(XPCJSContext::RecordScriptActivity(aActive)) {} + +AutoScriptActivity::~AutoScriptActivity() { + MOZ_ALWAYS_TRUE(mActive == XPCJSContext::RecordScriptActivity(mOldValue)); +} + +static const double sChromeSlowScriptTelemetryCutoff(10.0); +static bool sTelemetryEventEnabled(false); + +// static +bool XPCJSContext::InterruptCallback(JSContext* cx) { + XPCJSContext* self = XPCJSContext::Get(); + + // Now is a good time to turn on profiling if it's pending. + PROFILER_JS_INTERRUPT_CALLBACK(); + + if (profiler_thread_is_being_profiled_for_markers()) { + nsDependentCString filename("unknown file"); + JS::AutoFilename scriptFilename; + // Computing the line number can be very expensive (see bug 1330231 for + // example), so don't request it here. + if (JS::DescribeScriptedCaller(cx, &scriptFilename)) { + if (const char* file = scriptFilename.get()) { + filename.Assign(file, strlen(file)); + } + PROFILER_MARKER_TEXT("JS::InterruptCallback", JS, {}, filename); + } + } + + // Normally we record mSlowScriptCheckpoint when we start to process an + // event. However, we can run JS outside of event handlers. This code takes + // care of that case. + if (self->mSlowScriptCheckpoint.IsNull()) { + self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); + self->mSlowScriptSecondHalf = false; + self->mSlowScriptActualWait = mozilla::TimeDuration(); + self->mTimeoutAccumulated = false; + self->mExecutedChromeScript = false; + return true; + } + + // Sometimes we get called back during XPConnect initialization, before Gecko + // has finished bootstrapping. Avoid crashing in nsContentUtils below. + if (!nsContentUtils::IsInitialized()) { + return true; + } + + // This is at least the second interrupt callback we've received since + // returning to the event loop. See how long it's been, and what the limit + // is. + TimeStamp now = TimeStamp::NowLoRes(); + TimeDuration duration = now - self->mSlowScriptCheckpoint; + int32_t limit; + + nsString addonId; + const char* prefName; + auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx)); + bool chrome = principal->Is<SystemPrincipal>(); + if (chrome) { + prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME; + limit = StaticPrefs::dom_max_chrome_script_run_time(); + self->mExecutedChromeScript = true; + } else if (auto policy = principal->ContentScriptAddonPolicy()) { + policy->GetId(addonId); + prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT; + limit = StaticPrefs::dom_max_ext_content_script_run_time(); + } else { + prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT; + limit = StaticPrefs::dom_max_script_run_time(); + } + + // When the parent process slow script dialog is disabled, we still want + // to be able to track things for telemetry, so set `mSlowScriptSecondHalf` + // to true in that case: + if (limit == 0 && chrome && + duration.ToSeconds() > sChromeSlowScriptTelemetryCutoff / 2.0) { + self->mSlowScriptSecondHalf = true; + return true; + } + // If there's no limit, or we're within the limit, let it go. + if (limit == 0 || duration.ToSeconds() < limit / 2.0) { + return true; + } + + self->mSlowScriptCheckpoint = now; + self->mSlowScriptActualWait += duration; + + // In order to guard against time changes or laptops going to sleep, we + // don't trigger the slow script warning until (limit/2) seconds have + // elapsed twice. + if (!self->mSlowScriptSecondHalf) { + self->mSlowScriptSecondHalf = true; + return true; + } + + // For scripts in content processes, we only want to show the slow script + // dialogue if the user is actually trying to perform an important + // interaction. In theory this could be a chrome script running in the + // content process, which we probably don't want to give the user the ability + // to terminate. However, if this is the case we won't be able to map the + // script global to a window and we'll bail out below. + if (XRE_IsContentProcess() && + StaticPrefs::dom_max_script_run_time_require_critical_input()) { + // Call possibly slow PeekMessages after the other common early returns in + // this method. + ContentChild* contentChild = ContentChild::GetSingleton(); + mozilla::ipc::MessageChannel* channel = + contentChild ? contentChild->GetIPCChannel() : nullptr; + if (channel) { + bool foundInputEvent = false; + channel->PeekMessages( + [&foundInputEvent](const IPC::Message& aMsg) -> bool { + if (nsContentUtils::IsMessageCriticalInputEvent(aMsg)) { + foundInputEvent = true; + return false; + } + return true; + }); + if (!foundInputEvent) { + return true; + } + } + } + + // We use a fixed value of 2 from browser_parent_process_hang_telemetry.js + // to check if the telemetry events work. Do not interrupt it with a dialog. + if (chrome && limit == 2 && xpc::IsInAutomation()) { + return true; + } + + // + // This has gone on long enough! Time to take action. ;-) + // + + // Get the DOM window associated with the running script. If the script is + // running in a non-DOM scope, we have to just let it keep running. + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + RefPtr<nsGlobalWindowInner> win = WindowOrNull(global); + if (!win) { + // If this is a sandbox associated with a DOMWindow via a + // sandboxPrototype, use that DOMWindow. This supports WebExtension + // content scripts. + win = SandboxWindowOrNull(global, cx); + } + + if (!win) { + NS_WARNING("No active window"); + return true; + } + + if (win->IsDying()) { + // The window is being torn down. When that happens we try to prevent + // the dispatch of new runnables, so it also makes sense to kill any + // long-running script. The user is primarily interested in this page + // going away. + return false; + } + + // Accumulate slow script invokation delay. + if (!chrome && !self->mTimeoutAccumulated) { + uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - + (limit * 1000.0)); + Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay); + self->mTimeoutAccumulated = true; + } + + // Show the prompt to the user, and kill if requested. + nsGlobalWindowInner::SlowScriptResponse response = win->ShowSlowScriptDialog( + cx, addonId, self->mSlowScriptActualWait.ToMilliseconds()); + if (response == nsGlobalWindowInner::KillSlowScript) { + if (Preferences::GetBool("dom.global_stop_script", true)) { + xpc::Scriptability::Get(global).Block(); + } + return false; + } + + // The user chose to continue the script. Reset the timer, and disable this + // machinery with a pref if the user opted out of future slow-script dialogs. + if (response != nsGlobalWindowInner::ContinueSlowScriptAndKeepNotifying) { + self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); + } + + if (response == nsGlobalWindowInner::AlwaysContinueSlowScript) { + Preferences::SetInt(prefName, 0); + } + + return true; +} + +#define JS_OPTIONS_DOT_STR "javascript.options." + +static mozilla::Atomic<bool> sDiscardSystemSource(false); + +bool xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; } + +static mozilla::Atomic<bool> sSharedMemoryEnabled(false); +static mozilla::Atomic<bool> sStreamsEnabled(false); + +static mozilla::Atomic<bool> sPropertyErrorMessageFixEnabled(false); +static mozilla::Atomic<bool> sWeakRefsEnabled(false); +static mozilla::Atomic<bool> sWeakRefsExposeCleanupSome(false); +static mozilla::Atomic<bool> sIteratorHelpersEnabled(false); +static mozilla::Atomic<bool> sShadowRealmsEnabled(false); +#ifdef NIGHTLY_BUILD +static mozilla::Atomic<bool> sArrayGroupingEnabled(false); +static mozilla::Atomic<bool> sWellFormedUnicodeStringsEnabled(false); +#endif +static mozilla::Atomic<bool> sChangeArrayByCopyEnabled(false); +static mozilla::Atomic<bool> sArrayFromAsyncEnabled(true); +#ifdef ENABLE_NEW_SET_METHODS +static mozilla::Atomic<bool> sEnableNewSetMethods(false); +#endif + +static JS::WeakRefSpecifier GetWeakRefsEnabled() { + if (!sWeakRefsEnabled) { + return JS::WeakRefSpecifier::Disabled; + } + + if (sWeakRefsExposeCleanupSome) { + return JS::WeakRefSpecifier::EnabledWithCleanupSome; + } + + return JS::WeakRefSpecifier::EnabledWithoutCleanupSome; +} + +void xpc::SetPrefableRealmOptions(JS::RealmOptions& options) { + options.creationOptions() + .setSharedMemoryAndAtomicsEnabled(sSharedMemoryEnabled) + .setCoopAndCoepEnabled( + StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy() && + StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) + .setPropertyErrorMessageFixEnabled(sPropertyErrorMessageFixEnabled) + .setWeakRefsEnabled(GetWeakRefsEnabled()) + .setIteratorHelpersEnabled(sIteratorHelpersEnabled) + .setShadowRealmsEnabled(sShadowRealmsEnabled) +#ifdef NIGHTLY_BUILD + .setArrayGroupingEnabled(sArrayGroupingEnabled) + .setWellFormedUnicodeStringsEnabled(sWellFormedUnicodeStringsEnabled) +#endif + .setChangeArrayByCopyEnabled(sChangeArrayByCopyEnabled) + .setArrayFromAsyncEnabled(sArrayFromAsyncEnabled) +#ifdef ENABLE_NEW_SET_METHODS + .setNewSetMethodsEnabled(sEnableNewSetMethods) +#endif + ; +} + +void xpc::SetPrefableContextOptions(JS::ContextOptions& options) { + options + .setAsmJS(Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs")) +#ifdef FUZZING + .setFuzzing(Preferences::GetBool(JS_OPTIONS_DOT_STR "fuzzing.enabled")) +#endif + .setWasm(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm")) + .setWasmForTrustedPrinciples( + Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_trustedprincipals")) + .setWasmIon(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_optimizingjit")) + .setWasmBaseline( + Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit")) +#define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \ + SHELL, PREF) \ + .setWasm##NAME(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_" PREF)) + JS_FOR_WASM_FEATURES(WASM_FEATURE, WASM_FEATURE, WASM_FEATURE) +#undef WASM_FEATURE + .setWasmVerbose(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_verbose")) + .setThrowOnAsmJSValidationFailure(Preferences::GetBool( + JS_OPTIONS_DOT_STR "throw_on_asmjs_validation_failure")) + .setSourcePragmas( + Preferences::GetBool(JS_OPTIONS_DOT_STR "source_pragmas")) + .setAsyncStack(Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack")) + .setAsyncStackCaptureDebuggeeOnly(Preferences::GetBool( + JS_OPTIONS_DOT_STR "asyncstack_capture_debuggee_only")) +#ifdef NIGHTLY_BUILD + .setImportAssertions(Preferences::GetBool( + JS_OPTIONS_DOT_STR "experimental.import_assertions")) +#endif + ; +} + +// Mirrored value of javascript.options.self_hosted.use_shared_memory. +static bool sSelfHostedUseSharedMemory = false; + +static void LoadStartupJSPrefs(XPCJSContext* xpccx) { + // Prefs that require a restart are handled here. This includes the + // process-wide JIT options because toggling these at runtime can easily cause + // races or get us into an inconsistent state. + // + // 'Live' prefs are handled by ReloadPrefsCallback below. + + JSContext* cx = xpccx->Context(); + + // Some prefs are unlisted in all.js / StaticPrefs (and thus are invisible in + // about:config). Make sure we use explicit defaults here. + bool useJitForTrustedPrincipals = + Preferences::GetBool(JS_OPTIONS_DOT_STR "jit_trustedprincipals", false); + bool disableWasmHugeMemory = Preferences::GetBool( + JS_OPTIONS_DOT_STR "wasm_disable_huge_memory", false); + + bool safeMode = false; + nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + xr->GetInSafeMode(&safeMode); + } + + // NOTE: Baseline Interpreter is still used in safe-mode. This gives a big + // perf gain and is our simplest JIT so we make a tradeoff. + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE, + StaticPrefs::javascript_options_blinterp_DoNotUseDirectly()); + + // Disable most JITs in Safe-Mode. + if (safeMode) { + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, false); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, false); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, false); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE, + false); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_JIT_HINTS_ENABLE, false); + sSelfHostedUseSharedMemory = false; + } else { + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_BASELINE_ENABLE, + StaticPrefs::javascript_options_baselinejit_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_ION_ENABLE, + StaticPrefs::javascript_options_ion_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption(cx, + JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE, + useJitForTrustedPrincipals); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE, + StaticPrefs::javascript_options_native_regexp_DoNotUseDirectly()); + // Only enable the jit hints cache for the content process to avoid + // any possible jank or delays on the parent process. + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_JIT_HINTS_ENABLE, + XRE_IsContentProcess() + ? StaticPrefs::javascript_options_jithints_DoNotUseDirectly() + : false); + sSelfHostedUseSharedMemory = StaticPrefs:: + javascript_options_self_hosted_use_shared_memory_DoNotUseDirectly(); + } + + JS_SetOffthreadIonCompilationEnabled( + cx, StaticPrefs:: + javascript_options_ion_offthread_compilation_DoNotUseDirectly()); + + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_BASELINE_INTERPRETER_WARMUP_TRIGGER, + StaticPrefs::javascript_options_blinterp_threshold_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, + StaticPrefs::javascript_options_baselinejit_threshold_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, + StaticPrefs::javascript_options_ion_threshold_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_ION_FREQUENT_BAILOUT_THRESHOLD, + StaticPrefs:: + javascript_options_ion_frequent_bailout_threshold_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_INLINING_BYTECODE_MAX_LENGTH, + StaticPrefs:: + javascript_options_inlining_bytecode_max_length_DoNotUseDirectly()); + +#ifdef DEBUG + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, + StaticPrefs::javascript_options_jit_full_debug_checks_DoNotUseDirectly()); +#endif + +#if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64) && \ + !defined(JS_CODEGEN_RISCV64) && !defined(JS_CODEGEN_LOONG64) + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, + StaticPrefs::javascript_options_spectre_index_masking_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS, + StaticPrefs:: + javascript_options_spectre_object_mitigations_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS, + StaticPrefs:: + javascript_options_spectre_string_mitigations_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, + StaticPrefs::javascript_options_spectre_value_masking_DoNotUseDirectly()); + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS, + StaticPrefs:: + javascript_options_spectre_jit_to_cxx_calls_DoNotUseDirectly()); +#endif + + JS_SetGlobalJitCompilerOption( + cx, JSJITCOMPILER_WATCHTOWER_MEGAMORPHIC, + StaticPrefs:: + javascript_options_watchtower_megamorphic_DoNotUseDirectly()); + + if (disableWasmHugeMemory) { + bool disabledHugeMemory = JS::DisableWasmHugeMemory(); + MOZ_RELEASE_ASSERT(disabledHugeMemory); + } + + JS::SetSiteBasedPretenuringEnabled( + StaticPrefs:: + javascript_options_site_based_pretenuring_DoNotUseDirectly()); +} + +static void ReloadPrefsCallback(const char* pref, void* aXpccx) { + // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above. + + auto xpccx = static_cast<XPCJSContext*>(aXpccx); + JSContext* cx = xpccx->Context(); + + sDiscardSystemSource = + Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource"); + sSharedMemoryEnabled = + Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory"); + sStreamsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams"); + sPropertyErrorMessageFixEnabled = + Preferences::GetBool(JS_OPTIONS_DOT_STR "property_error_message_fix"); + sWeakRefsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs"); + sWeakRefsExposeCleanupSome = Preferences::GetBool( + JS_OPTIONS_DOT_STR "experimental.weakrefs.expose_cleanupSome"); + sShadowRealmsEnabled = + Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.shadow_realms"); +#ifdef NIGHTLY_BUILD + sIteratorHelpersEnabled = + Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.iterator_helpers"); + sArrayGroupingEnabled = + Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.array_grouping"); + sWellFormedUnicodeStringsEnabled = Preferences::GetBool( + JS_OPTIONS_DOT_STR "experimental.well_formed_unicode_strings"); +#endif + sChangeArrayByCopyEnabled = Preferences::GetBool( + JS_OPTIONS_DOT_STR "experimental.enable_change_array_by_copy"); + sArrayFromAsyncEnabled = Preferences::GetBool( + JS_OPTIONS_DOT_STR "experimental.enable_array_from_async"); +#ifdef ENABLE_NEW_SET_METHODS + sEnableNewSetMethods = + Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.new_set_methods"); +#endif + +#ifdef JS_GC_ZEAL + int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1); + int32_t zeal_frequency = Preferences::GetInt( + JS_OPTIONS_DOT_STR "gczeal.frequency", JS_DEFAULT_ZEAL_FREQ); + if (zeal >= 0) { + JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency); + } +#endif // JS_GC_ZEAL + + auto& contextOptions = JS::ContextOptionsRef(cx); + SetPrefableContextOptions(contextOptions); + + // Set options not shared with workers. + contextOptions + .setThrowOnDebuggeeWouldRun(Preferences::GetBool( + JS_OPTIONS_DOT_STR "throw_on_debuggee_would_run")) + .setDumpStackOnDebuggeeWouldRun(Preferences::GetBool( + JS_OPTIONS_DOT_STR "dump_stack_on_debuggee_would_run")); + + JS::SetUseFdlibmForSinCosTan( + Preferences::GetBool(JS_OPTIONS_DOT_STR "use_fdlibm_for_sin_cos_tan")); + + nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + bool safeMode = false; + xr->GetInSafeMode(&safeMode); + if (safeMode) { + contextOptions.disableOptionsForSafeMode(); + } + } + + JS_SetParallelParsingEnabled( + cx, Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing")); +} + +XPCJSContext::~XPCJSContext() { + MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); + // Elsewhere we abort immediately if XPCJSContext initialization fails. + // Therefore the context must be non-null. + MOZ_ASSERT(MaybeContext()); + + Preferences::UnregisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, + this); + +#ifdef FUZZING + Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); +#endif + + // Clear any pending exception. It might be an XPCWrappedJS, and if we try + // to destroy it later we will crash. + SetPendingException(nullptr); + + // If we're the last XPCJSContext around, clean up the watchdog manager. + if (--sInstanceCount == 0) { + if (mWatchdogManager->GetWatchdog()) { + mWatchdogManager->StopWatchdog(); + } + + mWatchdogManager->UnregisterContext(this); + mWatchdogManager->Shutdown(); + sWatchdogInstance = nullptr; + } else { + // Otherwise, simply remove ourselves from the list. + mWatchdogManager->UnregisterContext(this); + } + + if (mCallContext) { + mCallContext->SystemIsBeingShutDown(); + } + + PROFILER_CLEAR_JS_CONTEXT(); +} + +XPCJSContext::XPCJSContext() + : mCallContext(nullptr), + mAutoRoots(nullptr), + mResolveName(JS::PropertyKey::Void()), + mResolvingWrapper(nullptr), + mWatchdogManager(GetWatchdogManager()), + mSlowScriptSecondHalf(false), + mTimeoutAccumulated(false), + mExecutedChromeScript(false), + mHasScriptActivity(false), + mPendingResult(NS_OK), + mActive(CONTEXT_INACTIVE), + mLastStateChange(PR_Now()) { + MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext); + MOZ_ASSERT(mWatchdogManager); + ++sInstanceCount; + mWatchdogManager->RegisterContext(this); +} + +/* static */ +XPCJSContext* XPCJSContext::Get() { + // Do an explicit null check, because this can get called from a process that + // does not run JS. + nsXPConnect* xpc = static_cast<nsXPConnect*>(nsXPConnect::XPConnect()); + return xpc ? xpc->GetContext() : nullptr; +} + +#ifdef XP_WIN +static size_t GetWindowsStackSize() { + // First, get the stack base. Because the stack grows down, this is the top + // of the stack. + const uint8_t* stackTop; +# ifdef _WIN64 + PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb()); + stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase); +# else + PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb()); + stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase); +# endif + + // Now determine the stack bottom. Note that we can't use tib->StackLimit, + // because that's the size of the committed area and we're also interested + // in the reserved pages below that. + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) { + MOZ_CRASH("VirtualQuery failed"); + } + + const uint8_t* stackBottom = + reinterpret_cast<const uint8_t*>(mbi.AllocationBase); + + // Do some sanity checks. + size_t stackSize = size_t(stackTop - stackBottom); + MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024); + MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024); + + // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like + // the guard page and large PGO stack frames. + return stackSize - 10 * sizeof(uintptr_t) * 1024; +} +#endif + +XPCJSRuntime* XPCJSContext::Runtime() const { + return static_cast<XPCJSRuntime*>(CycleCollectedJSContext::Runtime()); +} + +CycleCollectedJSRuntime* XPCJSContext::CreateRuntime(JSContext* aCx) { + return new XPCJSRuntime(aCx); +} + +class HelperThreadTaskHandler : public Task { + public: + bool Run() override { + JS::RunHelperThreadTask(); + return true; + } + explicit HelperThreadTaskHandler() : Task(false, EventQueuePriority::Normal) { + // Bug 1703185: Currently all tasks are run at the same priority. + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + bool GetName(nsACString& aName) override { + aName.AssignLiteral("HelperThreadTask"); + return true; + } +#endif + + private: + ~HelperThreadTaskHandler() = default; +}; + +static void DispatchOffThreadTask(JS::DispatchReason) { + TaskController::Get()->AddTask(MakeAndAddRef<HelperThreadTaskHandler>()); +} + +static bool CreateSelfHostedSharedMemory(JSContext* aCx, + JS::SelfHostedCache aBuf) { + auto& shm = xpc::SelfHostedShmem::GetSingleton(); + MOZ_RELEASE_ASSERT(shm.Content().IsEmpty()); + // Failures within InitFromParent output warnings but do not cause + // unrecoverable failures. + shm.InitFromParent(aBuf); + return true; +} + +nsresult XPCJSContext::Initialize() { + if (StaticPrefs::javascript_options_external_thread_pool_DoNotUseDirectly()) { + size_t threadCount = TaskController::GetPoolThreadCount(); + size_t stackSize = TaskController::GetThreadStackSize(); + SetHelperThreadTaskCallback(&DispatchOffThreadTask, threadCount, stackSize); + } + + nsresult rv = + CycleCollectedJSContext::Initialize(nullptr, JS::DefaultHeapMaxBytes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(Context()); + JSContext* cx = Context(); + + // The JS engine permits us to set different stack limits for system code, + // trusted script, and untrusted script. We have tests that ensure that + // we can always execute 10 "heavy" (eval+with) stack frames deeper in + // privileged code. Our stack sizes vary greatly in different configurations, + // so satisfying those tests requires some care. Manual measurements of the + // number of heavy stack frames achievable gives us the following rough data, + // ordered by the effective categories in which they are grouped in the + // JS_SetNativeStackQuota call (which predates this analysis). + // + // The following "Stack Frames" numbers come from `chromeLimit` in + // js/xpconnect/tests/chrome/test_bug732665.xul + // + // Platform | Build | Stack Quota | Stack Frames | Stack Frame Size + // ------------+-------+-------------+--------------+------------------ + // OSX 64 | Opt | 7MB | 1331 | ~5.4k + // OSX 64 | Debug | 7MB | 1202 | ~6.0k + // ------------+-------+-------------+--------------+------------------ + // Linux 32 | Opt | 7.875MB | 2513 | ~3.2k + // Linux 32 | Debug | 7.875MB | 2146 | ~3.8k + // ------------+-------+-------------+--------------+------------------ + // Linux 64 | Opt | 7.875MB | 1360 | ~5.9k + // Linux 64 | Debug | 7.875MB | 1180 | ~6.8k + // Linux 64 | ASan | 7.875MB | 473 | ~17.0k + // ------------+-------+-------------+--------------+------------------ + // Windows 32 | Opt | 984k | 188 | ~5.2k + // Windows 32 | Debug | 984k | 208 | ~4.7k + // ------------+-------+-------------+--------------+------------------ + // Windows 64 | Opt | 1.922MB | 189 | ~10.4k + // Windows 64 | Debug | 1.922MB | 175 | ~11.2k + // + // We tune the trusted/untrusted quotas for each configuration to achieve our + // invariants while attempting to minimize overhead. In contrast, our buffer + // between system code and trusted script is a very unscientific 10k. + const size_t kSystemCodeBuffer = 10 * 1024; + + // Our "default" stack is what we use in configurations where we don't have + // a compelling reason to do things differently. This is effectively 512KB + // on 32-bit platforms and 1MB on 64-bit platforms. + const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; + + // Set maximum stack size for different configurations. This value is then + // capped below because huge stacks are not web-compatible. + +#if defined(XP_MACOSX) || defined(DARWIN) + // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB, + // and give trusted script 180k extra. The stack is huge on mac anyway. + const size_t kUncappedStackQuota = 7 * 1024 * 1024; + const size_t kTrustedScriptBuffer = 180 * 1024; +#elif defined(XP_LINUX) && !defined(ANDROID) + // Most Linux distributions set default stack size to 8MB. Use it as the + // maximum value. + const size_t kStackQuotaMax = 8 * 1024 * 1024; +# if defined(MOZ_ASAN) || defined(DEBUG) + // Bug 803182: account for the 4x difference in the size of js::Interpret + // between optimized and debug builds. We use 2x since the JIT part + // doesn't increase much. + // See the standalone MOZ_ASAN branch below for the ASan case. + const size_t kStackQuotaMin = 2 * kDefaultStackQuota; +# else + const size_t kStackQuotaMin = kDefaultStackQuota; +# endif + // Allocate 128kB margin for the safe space. + const size_t kStackSafeMargin = 128 * 1024; + + struct rlimit rlim; + const size_t kUncappedStackQuota = + getrlimit(RLIMIT_STACK, &rlim) == 0 + ? std::max(std::min(size_t(rlim.rlim_cur - kStackSafeMargin), + kStackQuotaMax - kStackSafeMargin), + kStackQuotaMin) + : kStackQuotaMin; +# if defined(MOZ_ASAN) + // See the standalone MOZ_ASAN branch below for the ASan case. + const size_t kTrustedScriptBuffer = 450 * 1024; +# else + const size_t kTrustedScriptBuffer = 180 * 1024; +# endif +#elif defined(XP_WIN) + // 1MB is the default stack size on Windows. We use the -STACK linker flag + // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, so + // we determine the stack size at runtime. + const size_t kUncappedStackQuota = GetWindowsStackSize(); +# if defined(MOZ_ASAN) + // See the standalone MOZ_ASAN branch below for the ASan case. + const size_t kTrustedScriptBuffer = 450 * 1024; +# else + const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) + ? 180 * 1024 // win64 + : 120 * 1024; // win32 +# endif +#elif defined(MOZ_ASAN) + // ASan requires more stack space due to red-zones, so give it double the + // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements + // were not taken at the time of this writing, so we hazard a guess that + // ASAN builds have roughly thrice the stack overhead as normal builds. + // On normal builds, the largest stack frame size we might encounter is + // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k. + // + // FIXME: Does this branch make sense for Windows and Android? + // (See bug 1415195) + const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; + const size_t kTrustedScriptBuffer = 450 * 1024; +#elif defined(ANDROID) + // Android appears to have 1MB stacks. Allow the use of 3/4 of that size + // (768KB on 32-bit), since otherwise we can crash with a stack overflow + // when nearing the 1MB limit. + const size_t kUncappedStackQuota = + kDefaultStackQuota + kDefaultStackQuota / 2; + const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; +#else + // Catch-all configuration for other environments. +# if defined(DEBUG) + const size_t kUncappedStackQuota = 2 * kDefaultStackQuota; +# else + const size_t kUncappedStackQuota = kDefaultStackQuota; +# endif + // Given the numbers above, we use 50k and 100k trusted buffers on 32-bit + // and 64-bit respectively. + const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; +#endif + + // Avoid an unused variable warning on platforms where we don't use the + // default. + (void)kDefaultStackQuota; + + // Large stacks are not web-compatible so cap to a smaller value. + // See bug 1537609 and bug 1562700. + const size_t kStackQuotaCap = + StaticPrefs::javascript_options_main_thread_stack_quota_cap(); + const size_t kStackQuota = std::min(kUncappedStackQuota, kStackQuotaCap); + + JS_SetNativeStackQuota( + cx, kStackQuota, kStackQuota - kSystemCodeBuffer, + kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer); + + PROFILER_SET_JS_CONTEXT(cx); + + JS_AddInterruptCallback(cx, InterruptCallback); + + Runtime()->Initialize(cx); + + LoadStartupJSPrefs(this); + + // Watch for the JS boolean options. + ReloadPrefsCallback(nullptr, this); + Preferences::RegisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, + this); + +#ifdef FUZZING + Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); +#endif + + // Initialize the MIME type used for the bytecode cache, after calling + // SetProcessBuildIdOp and loading JS prefs. + if (!nsContentUtils::InitJSBytecodeMimeType()) { + NS_ABORT_OOM(0); // Size is unknown. + } + + // When available, set the self-hosted shared memory to be read, so that we + // can decode the self-hosted content instead of parsing it. + auto& shm = xpc::SelfHostedShmem::GetSingleton(); + JS::SelfHostedCache selfHostedContent = shm.Content(); + JS::SelfHostedWriter writer = nullptr; + if (XRE_IsParentProcess() && sSelfHostedUseSharedMemory) { + // Only the Parent process has permissions to write to the self-hosted + // shared memory. + writer = CreateSelfHostedSharedMemory; + } + + if (!JS::InitSelfHostedCode(cx, selfHostedContent, writer)) { + // Note: If no exception is pending, failure is due to OOM. + if (!JS_IsExceptionPending(cx) || JS_IsThrowingOutOfMemory(cx)) { + NS_ABORT_OOM(0); // Size is unknown. + } + + // Failed to execute self-hosted JavaScript! Uh oh. + MOZ_CRASH("InitSelfHostedCode failed"); + } + + MOZ_RELEASE_ASSERT(Runtime()->InitializeStrings(cx), + "InitializeStrings failed"); + + return NS_OK; +} + +// static +uint32_t XPCJSContext::sInstanceCount; + +// static +StaticAutoPtr<WatchdogManager> XPCJSContext::sWatchdogInstance; + +// static +WatchdogManager* XPCJSContext::GetWatchdogManager() { + if (sWatchdogInstance) { + return sWatchdogInstance; + } + + MOZ_ASSERT(sInstanceCount == 0); + sWatchdogInstance = new WatchdogManager(); + return sWatchdogInstance; +} + +// static +XPCJSContext* XPCJSContext::NewXPCJSContext() { + XPCJSContext* self = new XPCJSContext(); + nsresult rv = self->Initialize(); + if (rv == NS_ERROR_OUT_OF_MEMORY) { + mozalloc_handle_oom(0); + } else if (NS_FAILED(rv)) { + MOZ_CRASH("new XPCJSContext failed to initialize."); + } + + if (self->Context()) { + return self; + } + + MOZ_CRASH("new XPCJSContext failed to initialize."); +} + +void XPCJSContext::BeforeProcessTask(bool aMightBlock) { + MOZ_ASSERT(NS_IsMainThread()); + + // Start the slow script timer. + mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); + mSlowScriptSecondHalf = false; + mSlowScriptActualWait = mozilla::TimeDuration(); + mTimeoutAccumulated = false; + mExecutedChromeScript = false; + CycleCollectedJSContext::BeforeProcessTask(aMightBlock); +} + +void XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth) { + // Record hangs in the parent process for telemetry. + if (mSlowScriptSecondHalf && XRE_IsE10sParentProcess()) { + double hangDuration = (mozilla::TimeStamp::NowLoRes() - + mSlowScriptCheckpoint + mSlowScriptActualWait) + .ToSeconds(); + // We use the pref to test this code. + double limit = sChromeSlowScriptTelemetryCutoff; + if (xpc::IsInAutomation()) { + double prefLimit = StaticPrefs::dom_max_chrome_script_run_time(); + if (prefLimit > 0) { + limit = std::min(prefLimit, sChromeSlowScriptTelemetryCutoff); + } + } + if (hangDuration > limit) { + if (!sTelemetryEventEnabled) { + sTelemetryEventEnabled = true; + Telemetry::SetEventRecordingEnabled("slow_script_warning"_ns, true); + } + + auto uriType = mExecutedChromeScript ? "browser"_ns : "content"_ns; + // Use AppendFloat to avoid printf-type APIs using locale-specific + // decimal separators, when we definitely want a `.`. + nsCString durationStr; + durationStr.AppendFloat(hangDuration); + auto extra = Some<nsTArray<Telemetry::EventExtraEntry>>( + {Telemetry::EventExtraEntry{"hang_duration"_ns, durationStr}, + Telemetry::EventExtraEntry{"uri_type"_ns, uriType}}); + Telemetry::RecordEvent( + Telemetry::EventID::Slow_script_warning_Shown_Browser, Nothing(), + extra); + } + } + + // Now that we're back to the event loop, reset the slow script checkpoint. + mSlowScriptCheckpoint = mozilla::TimeStamp(); + mSlowScriptSecondHalf = false; + + // Call cycle collector occasionally. + MOZ_ASSERT(NS_IsMainThread()); + nsJSContext::MaybePokeCC(); + CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth); + + // This exception might have been set if we called an XPCWrappedJS that threw, + // but now we're returning to the event loop, so nothing is going to look at + // this value again. Clear it to prevent leaks. + SetPendingException(nullptr); +} + +void XPCJSContext::MaybePokeGC() { nsJSContext::MaybePokeGC(); } + +bool XPCJSContext::IsSystemCaller() const { + return nsContentUtils::IsSystemCaller(Context()); +} diff --git a/js/xpconnect/src/XPCJSID.cpp b/js/xpconnect/src/XPCJSID.cpp new file mode 100644 index 0000000000..565dc99c86 --- /dev/null +++ b/js/xpconnect/src/XPCJSID.cpp @@ -0,0 +1,626 @@ +/* -*- 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/. */ + +/* An xpcom implementation of the JavaScript nsIID and nsCID objects. */ + +#include "xpcprivate.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/Attributes.h" +#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot +#include "js/PropertyAndElement.h" // JS_DefineFunction, JS_DefineFunctionById, JS_DefineProperty, JS_DefinePropertyById +#include "js/Symbol.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +namespace xpc { + +/****************************************************************************** + * # Generic IDs # + * + * Generic IDs are the type of JS object created by most code which passes nsID + * objects to JavaScript code. They provide no special methods, and only store + * the raw nsID value. + * + * The nsID value is stored in 4 reserved slots, with 32 bits of the 128-bit + * value stored in each slot. Getter code extracts this data, and combines them + * back into the nsID value. + */ +static bool ID_Equals(JSContext* aCx, unsigned aArgc, Value* aVp); +static bool ID_GetNumber(JSContext* aCx, unsigned aArgc, Value* aVp); + +// Generic ID objects contain 4 reserved slots, each containing a uint32_t with +// 1/4 of the representation of the nsID value. This allows us to avoid an extra +// allocation for the nsID object, and eliminates the need for a finalizer. +enum { kID_Slot0, kID_Slot1, kID_Slot2, kID_Slot3, kID_SlotCount }; +static const JSClass sID_Class = { + "nsJSID", JSCLASS_HAS_RESERVED_SLOTS(kID_SlotCount), JS_NULL_CLASS_OPS}; + +/****************************************************************************** + * # Interface IDs # + * + * In addition to the properties exposed by Generic ID objects, IID supports + * 'instanceof', exposes constant properties defined on the class, and exposes + * the interface name as the 'name' and 'toString()' values. + */ +static bool IID_HasInstance(JSContext* aCx, unsigned aArgc, Value* aVp); +static bool IID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp); + +static bool IID_NewEnumerate(JSContext* cx, HandleObject obj, + MutableHandleIdVector properties, + bool enumerableOnly); +static bool IID_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp); +static bool IID_MayResolve(const JSAtomState& names, jsid id, + JSObject* maybeObj); + +static const JSClassOps sIID_ClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + IID_NewEnumerate, // newEnumerate + IID_Resolve, // resolve + IID_MayResolve, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +// Interface ID objects use a single reserved slot containing a pointer to the +// nsXPTInterfaceInfo object for the interface in question. +enum { kIID_InfoSlot, kIID_SlotCount }; +static const JSClass sIID_Class = { + "nsJSIID", JSCLASS_HAS_RESERVED_SLOTS(kIID_SlotCount), &sIID_ClassOps}; + +/****************************************************************************** + * # Contract IDs # + * + * In addition to the properties exposed by Generic ID objects, Contract IDs + * expose 'getService' and 'createInstance' methods, and expose the contractID + * string as '.name' and '.toString()'. + */ +static bool CID_CreateInstance(JSContext* aCx, unsigned aArgc, Value* aVp); +static bool CID_GetService(JSContext* aCx, unsigned aArgc, Value* aVp); +static bool CID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp); + +// ContractID objects use a single reserved slot, containing the ContractID. The +// nsCID value for this object is looked up when the object is being unwrapped. +enum { kCID_ContractSlot, kCID_SlotCount }; +static const JSClass sCID_Class = { + "nsJSCID", JSCLASS_HAS_RESERVED_SLOTS(kCID_SlotCount), JS_NULL_CLASS_OPS}; + +/** + * Ensure that the nsID prototype objects have been created for the current + * global, and extract the prototype values. + */ +static JSObject* GetIDPrototype(JSContext* aCx, const JSClass* aClass) { + XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(aCx)); + if (NS_WARN_IF(!scope)) { + return nullptr; + } + + // Create prototype objects for the JSID objects if they haven't been + // created for this scope yet. + if (!scope->mIDProto) { + MOZ_ASSERT(!scope->mIIDProto && !scope->mCIDProto); + + RootedObject idProto(aCx, JS_NewPlainObject(aCx)); + RootedObject iidProto(aCx, + JS_NewObjectWithGivenProto(aCx, nullptr, idProto)); + RootedObject cidProto(aCx, + JS_NewObjectWithGivenProto(aCx, nullptr, idProto)); + RootedId hasInstance(aCx, + GetWellKnownSymbolKey(aCx, SymbolCode::hasInstance)); + + const uint32_t kFlags = + JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT; + const uint32_t kNoEnum = JSPROP_READONLY | JSPROP_PERMANENT; + + bool ok = + idProto && iidProto && cidProto && + // Methods and properties on all ID Objects: + JS_DefineFunction(aCx, idProto, "equals", ID_Equals, 1, kFlags) && + JS_DefineProperty(aCx, idProto, "number", ID_GetNumber, nullptr, + kFlags) && + + // Methods for IfaceID objects, which also inherit ID properties: + JS_DefineFunctionById(aCx, iidProto, hasInstance, IID_HasInstance, 1, + kNoEnum) && + JS_DefineProperty(aCx, iidProto, "name", IID_GetName, nullptr, + kFlags) && + + // Methods for ContractID objects, which also inherit ID properties: + JS_DefineFunction(aCx, cidProto, "createInstance", CID_CreateInstance, + 1, kFlags) && + JS_DefineFunction(aCx, cidProto, "getService", CID_GetService, 1, + kFlags) && + JS_DefineProperty(aCx, cidProto, "name", CID_GetName, nullptr, + kFlags) && + + // ToString returns '.number' on generic IDs, while returning + // '.name' on other ID types. + JS_DefineFunction(aCx, idProto, "toString", ID_GetNumber, 0, kFlags) && + JS_DefineFunction(aCx, iidProto, "toString", IID_GetName, 0, kFlags) && + JS_DefineFunction(aCx, cidProto, "toString", CID_GetName, 0, kFlags); + if (!ok) { + return nullptr; + } + + scope->mIDProto = idProto; + scope->mIIDProto = iidProto; + scope->mCIDProto = cidProto; + } + + if (aClass == &sID_Class) { + return scope->mIDProto; + } else if (aClass == &sIID_Class) { + return scope->mIIDProto; + } else if (aClass == &sCID_Class) { + return scope->mCIDProto; + } + + MOZ_CRASH("Unrecognized ID Object Class"); +} + +// Unwrap the given value to an object with the correct class, or nullptr. +static JSObject* GetIDObject(HandleValue aVal, const JSClass* aClass) { + if (aVal.isObject()) { + // We care only about IID/CID objects here, so CheckedUnwrapStatic is fine. + JSObject* obj = js::CheckedUnwrapStatic(&aVal.toObject()); + if (obj && JS::GetClass(obj) == aClass) { + return obj; + } + } + return nullptr; +} + +static const nsXPTInterfaceInfo* GetInterfaceInfo(JSObject* obj) { + MOZ_ASSERT(JS::GetClass(obj) == &sIID_Class); + return static_cast<const nsXPTInterfaceInfo*>( + JS::GetReservedSlot(obj, kIID_InfoSlot).toPrivate()); +} + +/** + * Unwrap an nsID object from a JSValue. + * + * For Generic ID objects, this function will extract the nsID from reserved + * slots. For IfaceID objects, it will be extracted from the nsXPTInterfaceInfo, + * and for ContractID objects, the ContractID's corresponding CID will be looked + * up. + */ +Maybe<nsID> JSValue2ID(JSContext* aCx, HandleValue aVal) { + if (!aVal.isObject()) { + return Nothing(); + } + + // We only care about ID objects here, so CheckedUnwrapStatic is fine. + RootedObject obj(aCx, js::CheckedUnwrapStatic(&aVal.toObject())); + if (!obj) { + return Nothing(); + } + + mozilla::Maybe<nsID> id; + if (JS::GetClass(obj) == &sID_Class) { + // Extract the raw bytes of the nsID from reserved slots. + uint32_t rawid[] = {JS::GetReservedSlot(obj, kID_Slot0).toPrivateUint32(), + JS::GetReservedSlot(obj, kID_Slot1).toPrivateUint32(), + JS::GetReservedSlot(obj, kID_Slot2).toPrivateUint32(), + JS::GetReservedSlot(obj, kID_Slot3).toPrivateUint32()}; + + // Construct a nsID inside the Maybe, and copy the rawid into it. + id.emplace(); + memcpy(id.ptr(), &rawid, sizeof(nsID)); + } else if (JS::GetClass(obj) == &sIID_Class) { + // IfaceID objects store a nsXPTInterfaceInfo* pointer. + const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj); + id.emplace(info->IID()); + } else if (JS::GetClass(obj) == &sCID_Class) { + // ContractID objects store a ContractID string. + JS::UniqueChars contractId = JS_EncodeStringToLatin1( + aCx, JS::GetReservedSlot(obj, kCID_ContractSlot).toString()); + + // NOTE(nika): If we directly access the nsComponentManager, we can do + // this with a more-basic pointer lookup: + // nsFactoryEntry* entry = nsComponentManagerImpl::gComponentManager-> + // GetFactoryEntry(contractId.ptr(), contractId.length()); + // if (entry) id.emplace(entry->mCIDEntry->cid); + + nsCOMPtr<nsIComponentRegistrar> registrar; + nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar)); + if (NS_FAILED(rv) || !registrar) { + return Nothing(); + } + + nsCID* cid = nullptr; + if (NS_SUCCEEDED(registrar->ContractIDToCID(contractId.get(), &cid))) { + id.emplace(*cid); + free(cid); + } + } + return id; +} + +/** + * Public ID Object Constructor Methods + */ +static JSObject* NewIDObjectHelper(JSContext* aCx, const JSClass* aClass) { + RootedObject proto(aCx, GetIDPrototype(aCx, aClass)); + if (proto) { + return JS_NewObjectWithGivenProto(aCx, aClass, proto); + } + return nullptr; +} + +bool ID2JSValue(JSContext* aCx, const nsID& aId, MutableHandleValue aVal) { + RootedObject obj(aCx, NewIDObjectHelper(aCx, &sID_Class)); + if (!obj) { + return false; + } + + // Get the data in nsID as 4 uint32_ts, and store them in slots. + uint32_t rawid[4]; + memcpy(&rawid, &aId, sizeof(nsID)); + static_assert(sizeof(nsID) == sizeof(rawid), "Wrong size of nsID"); + JS::SetReservedSlot(obj, kID_Slot0, PrivateUint32Value(rawid[0])); + JS::SetReservedSlot(obj, kID_Slot1, PrivateUint32Value(rawid[1])); + JS::SetReservedSlot(obj, kID_Slot2, PrivateUint32Value(rawid[2])); + JS::SetReservedSlot(obj, kID_Slot3, PrivateUint32Value(rawid[3])); + + aVal.setObject(*obj); + return true; +} + +bool IfaceID2JSValue(JSContext* aCx, const nsXPTInterfaceInfo& aInfo, + MutableHandleValue aVal) { + RootedObject obj(aCx, NewIDObjectHelper(aCx, &sIID_Class)); + if (!obj) { + return false; + } + + // The InterfaceInfo is stored in a reserved slot. + JS::SetReservedSlot(obj, kIID_InfoSlot, PrivateValue((void*)&aInfo)); + aVal.setObject(*obj); + return true; +} + +bool ContractID2JSValue(JSContext* aCx, JSString* aContract, + MutableHandleValue aVal) { + RootedString jsContract(aCx, aContract); + + { + // It is perfectly safe to have a ContractID object with an invalid + // ContractID, but is usually a bug. + nsCOMPtr<nsIComponentRegistrar> registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + if (!registrar) { + return false; + } + + bool registered = false; + JS::UniqueChars contract = JS_EncodeStringToLatin1(aCx, jsContract); + registrar->IsContractIDRegistered(contract.get(), ®istered); + if (!registered) { + return false; + } + } + + RootedObject obj(aCx, NewIDObjectHelper(aCx, &sCID_Class)); + if (!obj) { + return false; + } + + // The Contract is stored in a reserved slot. + JS::SetReservedSlot(obj, kCID_ContractSlot, StringValue(jsContract)); + aVal.setObject(*obj); + return true; +} + +/****************************************************************************** + * # Method & Property Getter Implementations # + */ + +// NOTE: This method is used both for 'get ID.prototype.number' and +// 'ID.prototype.toString'. +static bool ID_GetNumber(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + Maybe<nsID> id = JSValue2ID(aCx, args.thisv()); + if (!id) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + char buf[NSID_LENGTH]; + id->ToProvidedString(buf); + JSString* jsnum = JS_NewStringCopyZ(aCx, buf); + if (!jsnum) { + return Throw(aCx, NS_ERROR_OUT_OF_MEMORY); + } + + args.rval().setString(jsnum); + return true; +} + +static bool ID_Equals(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + if (!args.requireAtLeast(aCx, "nsID.equals", 1)) { + return false; + } + + Maybe<nsID> id = JSValue2ID(aCx, args.thisv()); + Maybe<nsID> id2 = JSValue2ID(aCx, args[0]); + if (!id || !id2) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + args.rval().setBoolean(id->Equals(*id2)); + return true; +} + +/* + * HasInstance hooks need to find an appropriate reflector in order to function + * properly. There are two complexities that we need to handle: + * + * 1 - Cross-compartment wrappers. Chrome uses over 100 compartments, all with + * system principal. The success of an instanceof check should not depend + * on which compartment an object comes from. At the same time, we want to + * make sure we don't unwrap important security wrappers. + * CheckedUnwrap does the right thing here. + * + * 2 - Prototype chains. Suppose someone creates a vanilla JS object |a| and + * sets its __proto__ to some WN |b|. If |b instanceof nsIFoo| returns true, + * one would expect |a instanceof nsIFoo| to return true as well, since + * instanceof is transitive up the prototype chain in ECMAScript. Moreover, + * there's chrome code that relies on this. + * + * This static method handles both complexities, returning either an XPCWN, a + * DOM object, or null. The object may well be cross-compartment from |cx|. + */ +static nsresult FindObjectForHasInstance(JSContext* cx, HandleObject objArg, + MutableHandleObject target) { + RootedObject obj(cx, objArg), proto(cx); + while (true) { + // Try the object, or the wrappee if allowed. We want CheckedUnwrapDynamic + // here, because we might in fact be looking for a Window. "cx" represents + // our current global. + JSObject* o = + js::IsWrapper(obj) ? js::CheckedUnwrapDynamic(obj, cx, false) : obj; + if (o && (IsWrappedNativeReflector(o) || IsDOMObject(o))) { + target.set(o); + return NS_OK; + } + + // Walk the prototype chain from the perspective of the callee (i.e. + // respecting Xrays if they exist). + if (!js::GetObjectProto(cx, obj, &proto)) { + return NS_ERROR_FAILURE; + } + if (!proto) { + target.set(nullptr); + return NS_OK; + } + obj = proto; + } +} + +nsresult HasInstance(JSContext* cx, HandleObject objArg, const nsID* iid, + bool* bp) { + *bp = false; + + RootedObject obj(cx); + nsresult rv = FindObjectForHasInstance(cx, objArg, &obj); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!obj) { + return NS_OK; + } + + // Need to unwrap Window correctly here, so use ReflectorToISupportsDynamic. + nsCOMPtr<nsISupports> identity = ReflectorToISupportsDynamic(obj, cx); + if (!identity) { + return NS_OK; + } + + nsCOMPtr<nsISupports> supp; + identity->QueryInterface(*iid, getter_AddRefs(supp)); + *bp = supp; + + // Our old HasInstance implementation operated by invoking FindTearOff on + // XPCWrappedNatives, and various bits of chrome JS came to depend on + // |instanceof| doing an implicit QI if it succeeds. Do a drive-by QI to + // preserve that behavior. This is just a compatibility hack, so we don't + // really care if it fails. + if (IsWrappedNativeReflector(obj)) { + (void)XPCWrappedNative::Get(obj)->FindTearOff(cx, *iid); + } + + return NS_OK; +} + +static bool IID_HasInstance(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + if (!args.requireAtLeast(aCx, "nsIID[Symbol.hasInstance]", 1)) { + return false; + } + + Maybe<nsID> id = JSValue2ID(aCx, args.thisv()); + if (!id) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + bool hasInstance = false; + if (args[0].isObject()) { + RootedObject target(aCx, &args[0].toObject()); + nsresult rv = HasInstance(aCx, target, id.ptr(), &hasInstance); + if (NS_FAILED(rv)) { + return Throw(aCx, rv); + } + } + args.rval().setBoolean(hasInstance); + return true; +} + +// NOTE: This method is used both for 'get IID.prototype.name' and +// 'IID.prototype.toString'. +static bool IID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + RootedObject obj(aCx, GetIDObject(args.thisv(), &sIID_Class)); + if (!obj) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj); + + // Name property is the name of the interface this nsIID was created from. + JSString* name = JS_NewStringCopyZ(aCx, info->Name()); + if (!name) { + return Throw(aCx, NS_ERROR_OUT_OF_MEMORY); + } + + args.rval().setString(name); + return true; +} + +static bool IID_NewEnumerate(JSContext* cx, HandleObject obj, + MutableHandleIdVector properties, + bool enumerableOnly) { + const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj); + + if (!properties.reserve(info->ConstantCount())) { + JS_ReportOutOfMemory(cx); + return false; + } + + RootedId id(cx); + RootedString name(cx); + for (uint16_t i = 0; i < info->ConstantCount(); ++i) { + name = JS_AtomizeString(cx, info->Constant(i).Name()); + if (!name || !JS_StringToId(cx, name, &id)) { + return false; + } + properties.infallibleAppend(id); + } + + return true; +} + +static bool IID_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp) { + *resolvedp = false; + if (!id.isString()) { + return true; + } + + JSLinearString* name = id.toLinearString(); + const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj); + for (uint16_t i = 0; i < info->ConstantCount(); ++i) { + if (JS_LinearStringEqualsAscii(name, info->Constant(i).Name())) { + *resolvedp = true; + + RootedValue constant(cx, info->Constant(i).JSValue()); + return JS_DefinePropertyById( + cx, obj, id, constant, + JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); + } + } + return true; +} + +static bool IID_MayResolve(const JSAtomState& names, jsid id, + JSObject* maybeObj) { + if (!id.isString()) { + return false; + } + + if (!maybeObj) { + // Each interface object has its own set of constants, so if we don't know + // the object, assume any string property may be resolved. + return true; + } + + JSLinearString* name = id.toLinearString(); + const nsXPTInterfaceInfo* info = GetInterfaceInfo(maybeObj); + for (uint16_t i = 0; i < info->ConstantCount(); ++i) { + if (JS_LinearStringEqualsAscii(name, info->Constant(i).Name())) { + return true; + } + } + return false; +} + +// Common code for CID_CreateInstance and CID_GetService +static bool CIGSHelper(JSContext* aCx, unsigned aArgc, Value* aVp, + bool aGetService) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + // Extract the ContractID string from our reserved slot. Don't use + // JSValue2ID as this method should only be defined on Contract ID objects, + // and it allows us to avoid a duplicate hashtable lookup. + RootedObject obj(aCx, GetIDObject(args.thisv(), &sCID_Class)); + if (!obj) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + JS::UniqueChars contractID = JS_EncodeStringToLatin1( + aCx, JS::GetReservedSlot(obj, kCID_ContractSlot).toString()); + + // Extract the IID from the first argument, if passed. Default: nsISupports. + Maybe<nsIID> iid = args.length() >= 1 ? JSValue2ID(aCx, args[0]) + : Some(NS_GET_IID(nsISupports)); + if (!iid) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + // Invoke CreateInstance or GetService with our ContractID. + nsresult rv; + nsCOMPtr<nsISupports> result; + if (aGetService) { + rv = CallGetService(contractID.get(), *iid, getter_AddRefs(result)); + if (NS_FAILED(rv) || !result) { + return Throw(aCx, NS_ERROR_XPC_GS_RETURNED_FAILURE); + } + } else { + rv = CallCreateInstance(contractID.get(), *iid, getter_AddRefs(result)); + if (NS_FAILED(rv) || !result) { + return Throw(aCx, NS_ERROR_XPC_CI_RETURNED_FAILURE); + } + } + + // Wrap the created object and return it. + rv = nsContentUtils::WrapNative(aCx, result, iid.ptr(), args.rval()); + if (NS_FAILED(rv) || args.rval().isPrimitive()) { + return Throw(aCx, NS_ERROR_XPC_CANT_CREATE_WN); + } + return true; +} + +static bool CID_CreateInstance(JSContext* aCx, unsigned aArgc, Value* aVp) { + return CIGSHelper(aCx, aArgc, aVp, /* aGetService = */ false); +} + +static bool CID_GetService(JSContext* aCx, unsigned aArgc, Value* aVp) { + return CIGSHelper(aCx, aArgc, aVp, /* aGetService = */ true); +} + +// NOTE: This method is used both for 'get CID.prototype.name' and +// 'CID.prototype.toString'. +static bool CID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + RootedObject obj(aCx, GetIDObject(args.thisv(), &sCID_Class)); + if (!obj) { + return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + // Return the string stored in our reserved ContractID slot. + args.rval().set(JS::GetReservedSlot(obj, kCID_ContractSlot)); + return true; +} + +} // namespace xpc diff --git a/js/xpconnect/src/XPCJSMemoryReporter.h b/js/xpconnect/src/XPCJSMemoryReporter.h new file mode 100644 index 0000000000..aa9954ec15 --- /dev/null +++ b/js/xpconnect/src/XPCJSMemoryReporter.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef XPCJSMemoryReporter_h +#define XPCJSMemoryReporter_h + +class nsISupports; +class nsIHandleReportCallback; + +namespace xpc { + +// The key is the window ID. +typedef nsTHashMap<nsUint64HashKey, nsCString> WindowPaths; + +// This is very nearly an instance of nsIMemoryReporter, but it's not, +// because it's invoked by nsWindowMemoryReporter in order to get |windowPaths| +// in CollectReports. +class JSReporter { + public: + static void CollectReports(WindowPaths* windowPaths, + WindowPaths* topWindowPaths, + nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize); +}; + +} // namespace xpc + +#endif diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp new file mode 100644 index 0000000000..2424cf9dda --- /dev/null +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -0,0 +1,3174 @@ +/* -*- 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/. */ + +/* Per JSRuntime object */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +#include "xpcprivate.h" +#include "xpcpublic.h" +#include "XPCMaps.h" +#include "XPCWrapper.h" +#include "XPCJSMemoryReporter.h" +#include "XrayWrapper.h" +#include "WrapperFactory.h" +#include "mozJSModuleLoader.h" +#include "nsNetUtil.h" +#include "nsContentSecurityUtils.h" + +#include "nsExceptionHandler.h" +#include "nsIMemoryInfoDumper.h" +#include "nsIMemoryReporter.h" +#include "nsIObserverService.h" +#include "mozilla/dom/Document.h" +#include "nsIRunnable.h" +#include "nsIPlatformInfo.h" +#include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" +#include "nsScriptSecurityManager.h" +#include "nsThreadPool.h" +#include "nsWindowSizes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Services.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "nsContentUtils.h" +#include "nsCCUncollectableMarker.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollector.h" +#include "jsapi.h" +#include "js/BuildId.h" // JS::BuildIdCharVector, JS::SetProcessBuildIdOp +#include "js/experimental/SourceHook.h" // js::{,Set}SourceHook +#include "js/GCAPI.h" +#include "js/MemoryFunctions.h" +#include "js/MemoryMetrics.h" +#include "js/Object.h" // JS::GetClass +#include "js/RealmIterators.h" +#include "js/SliceBudget.h" +#include "js/UbiNode.h" +#include "js/UbiNodeUtils.h" +#include "js/friend/UsageStatistics.h" // JSMetric, JS_SetAccumulateTelemetryCallback +#include "js/friend/WindowProxy.h" // js::SetWindowProxyClass +#include "js/friend/XrayJitInfo.h" // JS::SetXrayJitInfo +#include "mozilla/dom/AbortSignalBinding.h" +#include "mozilla/dom/GeneratedAtomList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FetchUtil.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "AccessCheck.h" +#include "nsGlobalWindow.h" +#include "nsAboutProtocolUtils.h" + +#include "NodeUbiReporting.h" +#include "ExpandedPrincipal.h" +#include "nsIInputStream.h" +#include "nsJSPrincipals.h" +#include "nsJSEnvironment.h" +#include "XPCInlines.h" + +#ifdef XP_WIN +# include <windows.h> +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpc; +using namespace JS; +using namespace js; +using mozilla::dom::PerThreadAtomCache; + +/***************************************************************************/ + +const char* const XPCJSRuntime::mStrings[] = { + "constructor", // IDX_CONSTRUCTOR + "toString", // IDX_TO_STRING + "toSource", // IDX_TO_SOURCE + "value", // IDX_VALUE + "QueryInterface", // IDX_QUERY_INTERFACE + "Components", // IDX_COMPONENTS + "Cc", // IDX_CC + "Ci", // IDX_CI + "Cr", // IDX_CR + "Cu", // IDX_CU + "Services", // IDX_SERVICES + "wrappedJSObject", // IDX_WRAPPED_JSOBJECT + "prototype", // IDX_PROTOTYPE + "eval", // IDX_EVAL + "controllers", // IDX_CONTROLLERS + "Controllers", // IDX_CONTROLLERS_CLASS + "length", // IDX_LENGTH + "name", // IDX_NAME + "undefined", // IDX_UNDEFINED + "", // IDX_EMPTYSTRING + "fileName", // IDX_FILENAME + "lineNumber", // IDX_LINENUMBER + "columnNumber", // IDX_COLUMNNUMBER + "stack", // IDX_STACK + "message", // IDX_MESSAGE + "cause", // IDX_CAUSE + "errors", // IDX_ERRORS + "lastIndex", // IDX_LASTINDEX + "then", // IDX_THEN + "isInstance", // IDX_ISINSTANCE + "Infinity", // IDX_INFINITY + "NaN", // IDX_NAN + "classId", // IDX_CLASS_ID + "interfaceId", // IDX_INTERFACE_ID + "initializer", // IDX_INITIALIZER + "print", // IDX_PRINT + "fetch", // IDX_FETCH + "crypto", // IDX_CRYPTO + "indexedDB", // IDX_INDEXEDDB + "structuredClone", // IDX_STRUCTUREDCLONE +}; + +/***************************************************************************/ + +// *Some* NativeSets are referenced from mClassInfo2NativeSetMap. +// *All* NativeSets are referenced from mNativeSetMap. +// So, in mClassInfo2NativeSetMap we just clear references to the unmarked. +// In mNativeSetMap we clear the references to the unmarked *and* delete them. + +class AsyncFreeSnowWhite : public Runnable { + public: + NS_IMETHOD Run() override { + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC); + AUTO_PROFILER_LABEL("AsyncFreeSnowWhite::Run", GCCC_FreeSnowWhite); + + TimeStamp start = TimeStamp::Now(); + // 2 ms budget, given that kICCSliceBudget is only 3 ms + js::SliceBudget budget = js::SliceBudget(js::TimeBudget(2)); + bool hadSnowWhiteObjects = + nsCycleCollector_doDeferredDeletionWithBudget(budget); + Telemetry::Accumulate( + Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING, + uint32_t((TimeStamp::Now() - start).ToMilliseconds())); + if (hadSnowWhiteObjects && !mContinuation) { + mContinuation = true; + if (NS_FAILED(Dispatch())) { + mActive = false; + } + } else { + mActive = false; + } + return NS_OK; + } + + nsresult Dispatch() { + nsCOMPtr<nsIRunnable> self(this); + return NS_DispatchToCurrentThreadQueue(self.forget(), 500, + EventQueuePriority::Idle); + } + + void Start(bool aContinuation = false, bool aPurge = false) { + if (mContinuation) { + mContinuation = aContinuation; + } + mPurge = aPurge; + if (!mActive && NS_SUCCEEDED(Dispatch())) { + mActive = true; + } + } + + AsyncFreeSnowWhite() + : Runnable("AsyncFreeSnowWhite"), + mContinuation(false), + mActive(false), + mPurge(false) {} + + public: + bool mContinuation; + bool mActive; + bool mPurge; +}; + +namespace xpc { + +CompartmentPrivate::CompartmentPrivate( + JS::Compartment* c, mozilla::UniquePtr<XPCWrappedNativeScope> scope, + mozilla::BasePrincipal* origin, const SiteIdentifier& site) + : originInfo(origin, site), + wantXrays(false), + allowWaivers(true), + isWebExtensionContentScript(false), + isUAWidgetCompartment(false), + hasExclusiveExpandos(false), + wasShutdown(false), + mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()), + mScope(std::move(scope)) { + MOZ_COUNT_CTOR(xpc::CompartmentPrivate); +} + +CompartmentPrivate::~CompartmentPrivate() { + MOZ_COUNT_DTOR(xpc::CompartmentPrivate); +} + +void CompartmentPrivate::SystemIsBeingShutDown() { + // We may call this multiple times when the compartment contains more than one + // realm. + if (!wasShutdown) { + mWrappedJSMap->ShutdownMarker(); + wasShutdown = true; + } +} + +RealmPrivate::RealmPrivate(JS::Realm* realm) : scriptability(realm) { + mozilla::PodArrayZero(wrapperDenialWarnings); +} + +/* static */ +void RealmPrivate::Init(HandleObject aGlobal, const SiteIdentifier& aSite) { + MOZ_ASSERT(aGlobal); + DebugOnly<const JSClass*> clasp = JS::GetClass(aGlobal); + MOZ_ASSERT(clasp->slot0IsISupports() || dom::IsDOMClass(clasp)); + + Realm* realm = GetObjectRealmOrNull(aGlobal); + + // Create the realm private. + RealmPrivate* realmPriv = new RealmPrivate(realm); + MOZ_ASSERT(!GetRealmPrivate(realm)); + SetRealmPrivate(realm, realmPriv); + + nsIPrincipal* principal = GetRealmPrincipal(realm); + Compartment* c = JS::GetCompartment(aGlobal); + + // Create the compartment private if needed. + if (CompartmentPrivate* priv = CompartmentPrivate::Get(c)) { + MOZ_ASSERT(priv->originInfo.IsSameOrigin(principal)); + } else { + auto scope = mozilla::MakeUnique<XPCWrappedNativeScope>(c, aGlobal); + priv = new CompartmentPrivate(c, std::move(scope), + BasePrincipal::Cast(principal), aSite); + JS_SetCompartmentPrivate(c, priv); + } +} + +// As XPCJSRuntime can live longer than when we shutdown the observer service, +// we have our own getter to account for this. +static nsCOMPtr<nsIObserverService> GetObserverService() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { + return nullptr; + } + return mozilla::services::GetObserverService(); +} + +static bool TryParseLocationURICandidate( + const nsACString& uristr, RealmPrivate::LocationHint aLocationHint, + nsIURI** aURI) { + static constexpr auto kGRE = "resource://gre/"_ns; + static constexpr auto kToolkit = "chrome://global/"_ns; + static constexpr auto kBrowser = "chrome://browser/"_ns; + + if (aLocationHint == RealmPrivate::LocationHintAddon) { + // Blacklist some known locations which are clearly not add-on related. + if (StringBeginsWith(uristr, kGRE) || StringBeginsWith(uristr, kToolkit) || + StringBeginsWith(uristr, kBrowser)) { + return false; + } + + // -- GROSS HACK ALERT -- + // The Yandex Elements 8.10.2 extension implements its own "xb://" URL + // scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up + // calling into the extension's own JS-implemented nsIProtocolHandler + // object, which we can't allow while we're iterating over the JS heap. + // So just skip any such URL. + // -- GROSS HACK ALERT -- + if (StringBeginsWith(uristr, "xb"_ns)) { + return false; + } + } + + nsCOMPtr<nsIURI> uri; + if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) { + return false; + } + + nsAutoCString scheme; + if (NS_FAILED(uri->GetScheme(scheme))) { + return false; + } + + // Cannot really map data: and blob:. + // Also, data: URIs are pretty memory hungry, which is kinda bad + // for memory reporter use. + if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) { + return false; + } + + uri.forget(aURI); + return true; +} + +bool RealmPrivate::TryParseLocationURI(RealmPrivate::LocationHint aLocationHint, + nsIURI** aURI) { + if (!aURI) { + return false; + } + + // Need to parse the URI. + if (location.IsEmpty()) { + return false; + } + + // Handle Sandbox location strings. + // A sandbox string looks like this, for anonymous sandboxes, and builds + // where Sandbox location tagging is enabled: + // + // <sandboxName> (from: <js-stack-frame-filename>:<lineno>) + // + // where <sandboxName> is user-provided via Cu.Sandbox() + // and <js-stack-frame-filename> and <lineno> is the stack frame location + // from where Cu.Sandbox was called. + // + // Otherwise, it is simply the caller-provided name, which is usually a URI. + // + // <js-stack-frame-filename> furthermore is "free form", often using a + // "uri -> uri -> ..." chain. The following code will and must handle this + // common case. + // + // It should be noted that other parts of the code may already rely on the + // "format" of these strings. + + static const nsDependentCString from("(from: "); + static const nsDependentCString arrow(" -> "); + static const size_t fromLength = from.Length(); + static const size_t arrowLength = arrow.Length(); + + // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName + int32_t idx = location.Find(from); + if (idx < 0) { + return TryParseLocationURICandidate(location, aLocationHint, aURI); + } + + // When parsing we're looking for the right-most URI. This URI may be in + // <sandboxName>, so we try this first. + if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint, + aURI)) { + return true; + } + + // Not in <sandboxName> so we need to inspect <js-stack-frame-filename> and + // the chain that is potentially contained within and grab the rightmost + // item that is actually a URI. + + // First, hack off the :<lineno>) part as well + int32_t ridx = location.RFind(":"_ns); + nsAutoCString chain( + Substring(location, idx + fromLength, ridx - idx - fromLength)); + + // Loop over the "->" chain. This loop also works for non-chains, or more + // correctly chains with only one item. + for (;;) { + idx = chain.RFind(arrow); + if (idx < 0) { + // This is the last chain item. Try to parse what is left. + return TryParseLocationURICandidate(chain, aLocationHint, aURI); + } + + // Try to parse current chain item + if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength), + aLocationHint, aURI)) { + return true; + } + + // Current chain item couldn't be parsed. + // Strip current item and continue. + chain = Substring(chain, 0, idx); + } + + MOZ_CRASH("Chain parser loop does not terminate"); +} + +static bool PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) { + // System principal gets a free pass. + if (aPrincipal->IsSystemPrincipal()) { + return true; + } + + auto* principal = BasePrincipal::Cast(aPrincipal); + + // ExpandedPrincipal gets a free pass. + if (principal->Is<ExpandedPrincipal>()) { + return true; + } + + // WebExtension principals get a free pass. + if (principal->AddonPolicy()) { + return true; + } + + // pdf.js is a special-case too. + if (nsContentUtils::IsPDFJS(principal)) { + return true; + } + + // Check whether our URI is an "about:" URI that allows scripts. If it is, + // we need to allow JS to run. + if (aPrincipal->SchemeIs("about")) { + uint32_t flags; + nsresult rv = aPrincipal->GetAboutModuleFlags(&flags); + if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) { + return true; + } + } + + return false; +} + +void RealmPrivate::RegisterStackFrame(JSStackFrameBase* aFrame) { + mJSStackFrames.PutEntry(aFrame); +} + +void RealmPrivate::UnregisterStackFrame(JSStackFrameBase* aFrame) { + mJSStackFrames.RemoveEntry(aFrame); +} + +void RealmPrivate::NukeJSStackFrames() { + for (const auto& key : mJSStackFrames.Keys()) { + key->Clear(); + } + + mJSStackFrames.Clear(); +} + +void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) { + RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); + if (!realmPrivate) { + return; + } + + realmPrivate->RegisterStackFrame(aStackFrame); +} + +void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame) { + RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); + if (!realmPrivate) { + return; + } + + realmPrivate->UnregisterStackFrame(aStackFrame); +} + +void NukeJSStackFrames(JS::Realm* aRealm) { + RealmPrivate* realmPrivate = RealmPrivate::Get(aRealm); + if (!realmPrivate) { + return; + } + + realmPrivate->NukeJSStackFrames(); +} + +Scriptability::Scriptability(JS::Realm* realm) + : mScriptBlocks(0), + mWindowAllowsScript(true), + mScriptBlockedByPolicy(false) { + nsIPrincipal* prin = nsJSPrincipals::get(JS::GetRealmPrincipals(realm)); + + mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); + if (mImmuneToScriptPolicy) { + return; + } + // If we're not immune, we should have a real principal with a URI. + // Check the principal against the new-style domain policy. + bool policyAllows; + nsresult rv = prin->GetIsScriptAllowedByPolicy(&policyAllows); + if (NS_SUCCEEDED(rv)) { + mScriptBlockedByPolicy = !policyAllows; + return; + } + // Something went wrong - be safe and block script. + mScriptBlockedByPolicy = true; +} + +bool Scriptability::Allowed() { + return mWindowAllowsScript && !mScriptBlockedByPolicy && mScriptBlocks == 0; +} + +bool Scriptability::IsImmuneToScriptPolicy() { return mImmuneToScriptPolicy; } + +void Scriptability::Block() { ++mScriptBlocks; } + +void Scriptability::Unblock() { + MOZ_ASSERT(mScriptBlocks > 0); + --mScriptBlocks; +} + +void Scriptability::SetWindowAllowsScript(bool aAllowed) { + mWindowAllowsScript = aAllowed || mImmuneToScriptPolicy; +} + +/* static */ +bool Scriptability::AllowedIfExists(JSObject* aScope) { + RealmPrivate* realmPrivate = RealmPrivate::Get(aScope); + return realmPrivate ? realmPrivate->scriptability.Allowed() : true; +} + +/* static */ +Scriptability& Scriptability::Get(JSObject* aScope) { + return RealmPrivate::Get(aScope)->scriptability; +} + +bool IsUAWidgetCompartment(JS::Compartment* compartment) { + // We always eagerly create compartment privates for UA Widget compartments. + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + return priv && priv->isUAWidgetCompartment; +} + +bool IsUAWidgetScope(JS::Realm* realm) { + return IsUAWidgetCompartment(JS::GetCompartmentForRealm(realm)); +} + +bool IsInUAWidgetScope(JSObject* obj) { + return IsUAWidgetCompartment(JS::GetCompartment(obj)); +} + +bool CompartmentOriginInfo::MightBeWebContent() const { + // Compartments with principals that are either the system principal or an + // expanded principal are definitely not web content. + return !nsContentUtils::IsSystemOrExpandedPrincipal(mOrigin); +} + +bool MightBeWebContentCompartment(JS::Compartment* compartment) { + if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) { + return priv->originInfo.MightBeWebContent(); + } + + // No CompartmentPrivate; try IsSystemCompartment. + return !js::IsSystemCompartment(compartment); +} + +bool CompartmentOriginInfo::IsSameOrigin(nsIPrincipal* aOther) const { + return mOrigin->FastEquals(aOther); +} + +/* static */ +bool CompartmentOriginInfo::Subsumes(JS::Compartment* aCompA, + JS::Compartment* aCompB) { + CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA); + CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB); + MOZ_ASSERT(apriv); + MOZ_ASSERT(bpriv); + return apriv->originInfo.mOrigin->FastSubsumes(bpriv->originInfo.mOrigin); +} + +/* static */ +bool CompartmentOriginInfo::SubsumesIgnoringFPD(JS::Compartment* aCompA, + JS::Compartment* aCompB) { + CompartmentPrivate* apriv = CompartmentPrivate::Get(aCompA); + CompartmentPrivate* bpriv = CompartmentPrivate::Get(aCompB); + MOZ_ASSERT(apriv); + MOZ_ASSERT(bpriv); + return apriv->originInfo.mOrigin->FastSubsumesIgnoringFPD( + bpriv->originInfo.mOrigin); +} + +void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment) { + // Note: we call this for all compartments that contain realms with a + // particular principal. Not all of these compartments have a + // CompartmentPrivate (for instance the temporary compartment/realm + // created by the JS engine for off-thread parsing). + if (CompartmentPrivate* priv = CompartmentPrivate::Get(compartment)) { + priv->originInfo.SetChangedDocumentDomain(); + } +} + +JSObject* UnprivilegedJunkScope() { + return XPCJSRuntime::Get()->UnprivilegedJunkScope(); +} + +JSObject* UnprivilegedJunkScope(const fallible_t&) { + return XPCJSRuntime::Get()->UnprivilegedJunkScope(fallible); +} + +bool IsUnprivilegedJunkScope(JSObject* obj) { + return XPCJSRuntime::Get()->IsUnprivilegedJunkScope(obj); +} + +JSObject* NACScope(JSObject* global) { + // If we're a chrome global, just use ourselves. + if (AccessCheck::isChrome(global)) { + return global; + } + + JSObject* scope = UnprivilegedJunkScope(); + JS::ExposeObjectToActiveJS(scope); + return scope; +} + +JSObject* PrivilegedJunkScope() { return XPCJSRuntime::Get()->LoaderGlobal(); } + +JSObject* CompilationScope() { return XPCJSRuntime::Get()->LoaderGlobal(); } + +nsGlobalWindowInner* WindowOrNull(JSObject* aObj) { + MOZ_ASSERT(aObj); + MOZ_ASSERT(!js::IsWrapper(aObj)); + + nsGlobalWindowInner* win = nullptr; + UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win); + return win; +} + +nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj) { + MOZ_ASSERT(aObj); + JSObject* glob = JS::GetNonCCWObjectGlobal(aObj); + + return WindowOrNull(glob); +} + +nsGlobalWindowInner* SandboxWindowOrNull(JSObject* aObj, JSContext* aCx) { + MOZ_ASSERT(aObj); + + if (!IsSandbox(aObj)) { + return nullptr; + } + + // Sandbox can't be a Proxy so it must have a static prototype. + JSObject* proto = GetStaticPrototype(aObj); + if (!proto || !IsSandboxPrototypeProxy(proto)) { + return nullptr; + } + + proto = js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false); + if (!proto) { + return nullptr; + } + return WindowOrNull(proto); +} + +nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) { + JSObject* glob = JS::CurrentGlobalOrNull(cx); + return glob ? WindowOrNull(glob) : nullptr; +} + +// Nukes all wrappers into or out of the given realm, and prevents new +// wrappers from being created. Additionally marks the realm as +// unscriptable after wrappers have been nuked. +// +// Note: This should *only* be called for browser or extension realms. +// Wrappers between web compartments must never be cut in web-observable +// ways. +void NukeAllWrappersForRealm( + JSContext* cx, JS::Realm* realm, + js::NukeReferencesToWindow nukeReferencesToWindow) { + // We do the following: + // * Nuke all wrappers into the realm. + // * Nuke all wrappers out of the realm's compartment, once we have nuked all + // realms in it. + js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm, + nukeReferencesToWindow, + js::NukeAllReferences); + + // Mark the realm as unscriptable. + xpc::RealmPrivate::Get(realm)->scriptability.Block(); +} + +} // namespace xpc + +static void CompartmentDestroyedCallback(JS::GCContext* gcx, + JS::Compartment* compartment) { + // NB - This callback may be called in JS_DestroyContext, which happens + // after the XPCJSRuntime has been torn down. + + // Get the current compartment private into a UniquePtr (which will do the + // cleanup for us), and null out the private (which may already be null). + mozilla::UniquePtr<CompartmentPrivate> priv( + CompartmentPrivate::Get(compartment)); + JS_SetCompartmentPrivate(compartment, nullptr); +} + +static size_t CompartmentSizeOfIncludingThisCallback( + MallocSizeOf mallocSizeOf, JS::Compartment* compartment) { + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0; +} + +/* + * Return true if there exists a non-system inner window which is a current + * inner window and whose reflector is gray. We don't merge system + * compartments, so we don't use them to trigger merging CCs. + */ +bool XPCJSRuntime::UsefulToMergeZones() const { + MOZ_ASSERT(NS_IsMainThread()); + + // Turns out, actually making this return true often enough makes Windows + // mochitest-gl OOM a lot. Need to figure out what's going on there; see + // bug 1277036. + + return false; +} + +void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc) { + if (CycleCollectedJSContext* ccx = GetContext()) { + const auto* cx = static_cast<const XPCJSContext*>(ccx); + if (AutoMarkingPtr* roots = cx->mAutoRoots) { + roots->TraceJSAll(trc); + } + } + + if (mIID2NativeInterfaceMap) { + mIID2NativeInterfaceMap->Trace(trc); + } + + dom::TraceBlackJS(trc); +} + +void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) { + XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc); +} + +void XPCJSRuntime::TraverseAdditionalNativeRoots( + nsCycleCollectionNoteRootCallback& cb) { + XPCWrappedNativeScope::SuspectAllWrappers(cb); + + auto* parti = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS); + for (auto* wjs : mSubjectToFinalizationWJS) { + MOZ_DIAGNOSTIC_ASSERT(wjs->IsSubjectToFinalization()); + cb.NoteXPCOMRoot(ToSupports(wjs), parti); + } +} + +void XPCJSRuntime::UnmarkSkippableJSHolders() { + CycleCollectedJSRuntime::UnmarkSkippableJSHolders(); +} + +void XPCJSRuntime::PrepareForForgetSkippable() { + nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr); + } +} + +void XPCJSRuntime::BeginCycleCollectionCallback(CCReason aReason) { + nsJSContext::BeginCycleCollectionCallback(aReason); + + nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr); + } +} + +void XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults& aResults) { + nsJSContext::EndCycleCollectionCallback(aResults); + + nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr); + } +} + +void XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation, bool aPurge) { + mAsyncSnowWhiteFreer->Start(aContinuation, aPurge); +} + +void xpc_UnmarkSkippableJSHolders() { + if (nsXPConnect::GetRuntimeInstance()) { + nsXPConnect::GetRuntimeInstance()->UnmarkSkippableJSHolders(); + } +} + +/* static */ +void XPCJSRuntime::GCSliceCallback(JSContext* cx, JS::GCProgress progress, + const JS::GCDescription& desc) { + XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); + if (!self) { + return; + } + + nsCOMPtr<nsIObserverService> obs = xpc::GetObserverService(); + if (obs) { + switch (progress) { + case JS::GC_CYCLE_BEGIN: + obs->NotifyObservers(nullptr, "garbage-collector-begin", nullptr); + break; + case JS::GC_CYCLE_END: + obs->NotifyObservers(nullptr, "garbage-collector-end", nullptr); + break; + default: + break; + } + } + + CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN); + + if (self->mPrevGCSliceCallback) { + (*self->mPrevGCSliceCallback)(cx, progress, desc); + } +} + +/* static */ +void XPCJSRuntime::DoCycleCollectionCallback(JSContext* cx) { + // The GC has detected that a CC at this point would collect a tremendous + // amount of garbage that is being revivified unnecessarily. + // + // The GC_WAITING reason is a little overloaded here, but we want to do + // a CC to allow Realms to be collected when they are referenced by a cycle. + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "XPCJSRuntime::DoCycleCollectionCallback", + []() { nsJSContext::CycleCollectNow(CCReason::GC_WAITING, nullptr); })); + + XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); + if (!self) { + return; + } + + if (self->mPrevDoCycleCollectionCallback) { + (*self->mPrevDoCycleCollectionCallback)(cx); + } +} + +void XPCJSRuntime::CustomGCCallback(JSGCStatus status) { + nsTArray<xpcGCCallback> callbacks(extraGCCallbacks.Clone()); + for (uint32_t i = 0; i < callbacks.Length(); ++i) { + callbacks[i](status); + } +} + +/* static */ +void XPCJSRuntime::FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status, + void* data) { + XPCJSRuntime* self = nsXPConnect::GetRuntimeInstance(); + if (!self) { + return; + } + + switch (status) { + case JSFINALIZE_GROUP_PREPARE: { + MOZ_ASSERT(!self->mDoingFinalization, "bad state"); + + MOZ_ASSERT(!self->mGCIsRunning, "bad state"); + self->mGCIsRunning = true; + + self->mDoingFinalization = true; + + break; + } + case JSFINALIZE_GROUP_START: { + MOZ_ASSERT(self->mDoingFinalization, "bad state"); + + MOZ_ASSERT(self->mGCIsRunning, "bad state"); + self->mGCIsRunning = false; + + break; + } + case JSFINALIZE_GROUP_END: { + MOZ_ASSERT(self->mDoingFinalization, "bad state"); + self->mDoingFinalization = false; + + break; + } + case JSFINALIZE_COLLECTION_END: { + MOZ_ASSERT(!self->mGCIsRunning, "bad state"); + self->mGCIsRunning = true; + + if (CycleCollectedJSContext* ccx = self->GetContext()) { + const auto* cx = static_cast<const XPCJSContext*>(ccx); + if (AutoMarkingPtr* roots = cx->mAutoRoots) { + roots->MarkAfterJSFinalizeAll(); + } + + // Now we are going to recycle any unused WrappedNativeTearoffs. + // We do this by iterating all the live callcontexts + // and marking the tearoffs in use. And then we + // iterate over all the WrappedNative wrappers and sweep their + // tearoffs. + // + // This allows us to perhaps minimize the growth of the + // tearoffs. And also makes us not hold references to interfaces + // on our wrapped natives that we are not actually using. + // + // XXX We may decide to not do this on *every* gc cycle. + + XPCCallContext* ccxp = cx->GetCallContext(); + while (ccxp) { + // Deal with the strictness of callcontext that + // complains if you ask for a tearoff when + // it is in a state where the tearoff could not + // possibly be valid. + if (ccxp->CanGetTearOff()) { + XPCWrappedNativeTearOff* to = ccxp->GetTearOff(); + if (to) { + to->Mark(); + } + } + ccxp = ccxp->GetPrevCallContext(); + } + } + + XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs(); + + // Now we need to kill the 'Dying' XPCWrappedNativeProtos. + // + // We transferred these native objects to this list when their JSObjects + // were finalized. We did not destroy them immediately at that point + // because the ordering of JS finalization is not deterministic and we did + // not yet know if any wrappers that might still be referencing the protos + // were still yet to be finalized and destroyed. We *do* know that the + // protos' JSObjects would not have been finalized if there were any + // wrappers that referenced the proto but were not themselves slated for + // finalization in this gc cycle. + // + // At this point we know that any and all wrappers that might have been + // referencing the protos in the dying list are themselves dead. So, we + // can safely delete all the protos in the list. + self->mDyingWrappedNativeProtos.clear(); + + MOZ_ASSERT(self->mGCIsRunning, "bad state"); + self->mGCIsRunning = false; + + break; + } + } +} + +/* static */ +void XPCJSRuntime::WeakPointerZonesCallback(JSTracer* trc, void* data) { + // Called before each sweeping slice -- after processing any final marking + // triggered by barriers -- to clear out any references to things that are + // about to be finalized and update any pointers to moved GC things. + XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data); + + // This callback is always called from within the GC so set the mGCIsRunning + // flag to prevent AssertInvalidWrappedJSNotInTable from trying to call back + // into the JS API. This has often already been set by FinalizeCallback by the + // time we get here, but it may not be if we are doing a shutdown GC or if we + // are called for compacting GC. + AutoRestore<bool> restoreState(self->mGCIsRunning); + self->mGCIsRunning = true; + + self->mWrappedJSMap->UpdateWeakPointersAfterGC(trc); + self->mUAWidgetScopeMap.traceWeak(trc); +} + +/* static */ +void XPCJSRuntime::WeakPointerCompartmentCallback(JSTracer* trc, + JS::Compartment* comp, + void* data) { + // Called immediately after the ZoneGroup weak pointer callback, but only + // once for each compartment that is being swept. + CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp); + if (xpcComp) { + xpcComp->UpdateWeakPointersAfterGC(trc); + } +} + +void CompartmentPrivate::UpdateWeakPointersAfterGC(JSTracer* trc) { + mRemoteProxies.traceWeak(trc); + mWrappedJSMap->UpdateWeakPointersAfterGC(trc); + mScope->UpdateWeakPointersAfterGC(trc); +} + +void XPCJSRuntime::CustomOutOfMemoryCallback() { + if (!Preferences::GetBool("memory.dump_reports_on_oom")) { + return; + } + + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + if (!dumper) { + return; + } + + // If this fails, it fails silently. + dumper->DumpMemoryInfoToTempDir(u"due-to-JS-OOM"_ns, + /* anonymize = */ false, + /* minimizeMemoryUsage = */ false); +} + +void XPCJSRuntime::OnLargeAllocationFailure() { + CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reporting); + + nsCOMPtr<nsIObserverService> os = xpc::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + } + + CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState::Reported); +} + +class LargeAllocationFailureRunnable final : public Runnable { + Mutex mMutex MOZ_UNANNOTATED; + CondVar mCondVar; + bool mWaiting; + + virtual ~LargeAllocationFailureRunnable() { MOZ_ASSERT(!mWaiting); } + + protected: + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + XPCJSRuntime::Get()->OnLargeAllocationFailure(); + + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mWaiting); + + mWaiting = false; + mCondVar.Notify(); + return NS_OK; + } + + public: + LargeAllocationFailureRunnable() + : mozilla::Runnable("LargeAllocationFailureRunnable"), + mMutex("LargeAllocationFailureRunnable::mMutex"), + mCondVar(mMutex, "LargeAllocationFailureRunnable::mCondVar"), + mWaiting(true) { + MOZ_ASSERT(!NS_IsMainThread()); + } + + void BlockUntilDone() { + MOZ_ASSERT(!NS_IsMainThread()); + + MutexAutoLock lock(mMutex); + while (mWaiting) { + mCondVar.Wait(); + } + } +}; + +static void OnLargeAllocationFailureCallback() { + // This callback can be called from any thread, including internal JS helper + // and DOM worker threads. We need to send the low-memory event via the + // observer service which can only be called on the main thread, so proxy to + // the main thread if we're not there already. The purpose of this callback + // is to synchronously free some memory so the caller can retry a failed + // allocation, so block on the completion. + + if (NS_IsMainThread()) { + XPCJSRuntime::Get()->OnLargeAllocationFailure(); + return; + } + + RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable; + if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) { + return; + } + + r->BlockUntilDone(); +} + +// Usually this is used through nsIPlatformInfo. However, being able to query +// this interface on all threads risk triggering some main-thread assertions +// which is not guaranteed by the callers of GetBuildId. +extern const char gToolkitBuildID[]; + +bool mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID) { + size_t length = std::char_traits<char>::length(gToolkitBuildID); + return aBuildID->append(gToolkitBuildID, length); +} + +size_t XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { + size_t n = 0; + n += mallocSizeOf(this); + n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); + n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf); + n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf); + n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf); + + n += CycleCollectedJSRuntime::SizeOfExcludingThis(mallocSizeOf); + + // There are other XPCJSRuntime members that could be measured; the above + // ones have been seen by DMD to be worth measuring. More stuff may be + // added later. + + return n; +} + +size_t CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { + size_t n = mallocSizeOf(this); + n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); + n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf); + return n; +} + +/***************************************************************************/ + +void XPCJSRuntime::Shutdown(JSContext* cx) { + // This destructor runs before ~CycleCollectedJSContext, which does the actual + // JS_DestroyContext() call. But destroying the context triggers one final GC, + // which can call back into the context with various callbacks if we aren't + // careful. Remove the relevant callbacks, but leave the weak pointer + // callbacks to clear out any remaining table entries. + JS_RemoveFinalizeCallback(cx, FinalizeCallback); + xpc_DelocalizeRuntime(JS_GetRuntime(cx)); + + JS::SetGCSliceCallback(cx, mPrevGCSliceCallback); + + nsScriptSecurityManager::ClearJSCallbacks(cx); + + // Clean up and destroy maps. Any remaining entries in mWrappedJSMap will be + // cleaned up by the weak pointer callbacks. + mIID2NativeInterfaceMap = nullptr; + + mClassInfo2NativeSetMap = nullptr; + + mNativeSetMap = nullptr; + + // Prevent ~LinkedList assertion failures if we leaked things. + mWrappedNativeScopes.clear(); + + mSubjectToFinalizationWJS.clear(); + + CycleCollectedJSRuntime::Shutdown(cx); +} + +XPCJSRuntime::~XPCJSRuntime() { + MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime); +} + +// If |*anonymizeID| is non-zero and this is a user realm, the name will +// be anonymized. +static void GetRealmName(JS::Realm* realm, nsCString& name, int* anonymizeID, + bool replaceSlashes) { + if (*anonymizeID && !js::IsSystemRealm(realm)) { + name.AppendPrintf("<anonymized-%d>", *anonymizeID); + *anonymizeID += 1; + } else if (JSPrincipals* principals = JS::GetRealmPrincipals(realm)) { + nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name); + if (NS_FAILED(rv)) { + name.AssignLiteral("(unknown)"); + } + + // If the realm's location (name) differs from the principal's script + // location, append the realm's location to allow differentiation of + // multiple realms owned by the same principal (e.g. components owned + // by the system or null principal). + RealmPrivate* realmPrivate = RealmPrivate::Get(realm); + if (realmPrivate) { + const nsACString& location = realmPrivate->GetLocation(); + if (!location.IsEmpty() && !location.Equals(name)) { + name.AppendLiteral(", "); + name.Append(location); + } + } + + if (*anonymizeID) { + // We might have a file:// URL that includes a path from the local + // filesystem, which should be omitted if we're anonymizing. + static const char* filePrefix = "file://"; + int filePos = name.Find(filePrefix); + if (filePos >= 0) { + int pathPos = filePos + strlen(filePrefix); + int lastSlashPos = -1; + for (int i = pathPos; i < int(name.Length()); i++) { + if (name[i] == '/' || name[i] == '\\') { + lastSlashPos = i; + } + } + if (lastSlashPos != -1) { + name.ReplaceLiteral(pathPos, lastSlashPos - pathPos, "<anonymized>"); + } else { + // Something went wrong. Anonymize the entire path to be + // safe. + name.Truncate(pathPos); + name += "<anonymized?!>"; + } + } + + // We might have a location like this: + // inProcessBrowserChildGlobal?ownedBy=http://www.example.com/ + // The owner should be omitted if it's not a chrome: URI and we're + // anonymizing. + static const char* ownedByPrefix = "inProcessBrowserChildGlobal?ownedBy="; + int ownedByPos = name.Find(ownedByPrefix); + if (ownedByPos >= 0) { + const char* chrome = "chrome:"; + int ownerPos = ownedByPos + strlen(ownedByPrefix); + const nsDependentCSubstring& ownerFirstPart = + Substring(name, ownerPos, strlen(chrome)); + if (!ownerFirstPart.EqualsASCII(chrome)) { + name.Truncate(ownerPos); + name += "<anonymized>"; + } + } + } + + // A hack: replace forward slashes with '\\' so they aren't + // treated as path separators. Users of the reporters + // (such as about:memory) have to undo this change. + if (replaceSlashes) { + name.ReplaceChar('/', '\\'); + } + } else { + name.AssignLiteral("null-principal"); + } +} + +extern void xpc::GetCurrentRealmName(JSContext* cx, nsCString& name) { + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + if (!global) { + name.AssignLiteral("no global"); + return; + } + + JS::Realm* realm = GetNonCCWObjectRealm(global); + int anonymizeID = 0; + GetRealmName(realm, name, &anonymizeID, false); +} + +void xpc::AddGCCallback(xpcGCCallback cb) { + XPCJSRuntime::Get()->AddGCCallback(cb); +} + +void xpc::RemoveGCCallback(xpcGCCallback cb) { + XPCJSRuntime::Get()->RemoveGCCallback(cb); +} + +static int64_t JSMainRuntimeGCHeapDistinguishedAmount() { + JSContext* cx = danger::GetJSContext(); + return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * js::gc::ChunkSize; +} + +static int64_t JSMainRuntimeTemporaryPeakDistinguishedAmount() { + JSContext* cx = danger::GetJSContext(); + return JS::PeakSizeOfTemporary(cx); +} + +static int64_t JSMainRuntimeCompartmentsSystemDistinguishedAmount() { + JSContext* cx = danger::GetJSContext(); + return JS::SystemCompartmentCount(cx); +} + +static int64_t JSMainRuntimeCompartmentsUserDistinguishedAmount() { + JSContext* cx = XPCJSContext::Get()->Context(); + return JS::UserCompartmentCount(cx); +} + +static int64_t JSMainRuntimeRealmsSystemDistinguishedAmount() { + JSContext* cx = danger::GetJSContext(); + return JS::SystemRealmCount(cx); +} + +static int64_t JSMainRuntimeRealmsUserDistinguishedAmount() { + JSContext* cx = XPCJSContext::Get()->Context(); + return JS::UserRealmCount(cx); +} + +class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter { + ~JSMainRuntimeTemporaryPeakReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES, + JSMainRuntimeTemporaryPeakDistinguishedAmount(), + "Peak transient data size in the main JSRuntime (the current size " + "of which is reported as " + "'explicit/js-non-window/runtime/temporary')."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter) + +// The REPORT* macros do an unconditional report. The ZRREPORT* macros are for +// realms and zones; they aggregate any entries smaller than +// SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap" +// entries for the realm. + +#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold() + +#define REPORT(_path, _kind, _units, _amount, _desc) \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ + nsIMemoryReporter::_units, _amount, \ + nsLiteralCString(_desc), data); + +#define REPORT_BYTES(_path, _kind, _amount, _desc) \ + REPORT(_path, _kind, UNITS_BYTES, _amount, _desc); + +#define REPORT_GC_BYTES(_path, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + gcTotal += amount; \ + } while (0) + +// Report realm/zone non-GC (KIND_HEAP) bytes. +#define ZRREPORT_BYTES(_path, _amount, _desc) \ + do { \ + /* Assign _descLiteral plus "" into a char* to prove that it's */ \ + /* actually a literal. */ \ + size_t amount = _amount; /* evaluate _amount only once */ \ + if (amount >= SUNDRIES_THRESHOLD) { \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_HEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + } else { \ + sundriesMallocHeap += amount; \ + } \ + } while (0) + +// Report realm/zone GC bytes. +#define ZRREPORT_GC_BYTES(_path, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + if (amount >= SUNDRIES_THRESHOLD) { \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + gcTotal += amount; \ + } else { \ + sundriesGCHeap += amount; \ + } \ + } while (0) + +// Report realm/zone non-heap bytes. +#define ZRREPORT_NONHEAP_BYTES(_path, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + if (amount >= SUNDRIES_THRESHOLD) { \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::KIND_NONHEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + } else { \ + sundriesNonHeap += amount; \ + } \ + } while (0) + +// Report runtime bytes. +#define RREPORT_BYTES(_path, _kind, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + rtTotal += amount; \ + } while (0) + +// Report GC thing bytes. +#define MREPORT_BYTES(_path, _kind, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(""_ns, _path, nsIMemoryReporter::_kind, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + nsLiteralCString(_desc), data); \ + gcThingTotal += amount; \ + } while (0) + +MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf) + +namespace xpc { + +static void ReportZoneStats(const JS::ZoneStats& zStats, + const xpc::ZoneStatsExtras& extras, + nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize, + size_t* gcTotalOut = nullptr) { + const nsCString& pathPrefix = extras.pathPrefix; + size_t gcTotal = 0; + size_t sundriesGCHeap = 0; + size_t sundriesMallocHeap = 0; + size_t sundriesNonHeap = 0; + + MOZ_ASSERT(!gcTotalOut == zStats.isTotals); + + ZRREPORT_GC_BYTES(pathPrefix + "symbols/gc-heap"_ns, zStats.symbolsGCHeap, + "Symbols."); + + ZRREPORT_GC_BYTES( + pathPrefix + "gc-heap-arena-admin"_ns, zStats.gcHeapArenaAdmin, + "Bookkeeping information and alignment padding within GC arenas."); + + ZRREPORT_GC_BYTES(pathPrefix + "unused-gc-things"_ns, + zStats.unusedGCThings.totalSize(), + "Unused GC thing cells within non-empty arenas."); + + ZRREPORT_BYTES(pathPrefix + "unique-id-map"_ns, zStats.uniqueIdMap, + "Address-independent cell identities."); + + ZRREPORT_BYTES(pathPrefix + "propmap-tables"_ns, zStats.initialPropMapTable, + "Tables storing property map information."); + + ZRREPORT_BYTES(pathPrefix + "shape-tables"_ns, zStats.shapeTables, + "Tables storing shape information."); + + ZRREPORT_BYTES(pathPrefix + "compartments/compartment-objects"_ns, + zStats.compartmentObjects, + "The JS::Compartment objects in this zone."); + + ZRREPORT_BYTES( + pathPrefix + "compartments/cross-compartment-wrapper-tables"_ns, + zStats.crossCompartmentWrappersTables, + "The cross-compartment wrapper tables."); + + ZRREPORT_BYTES( + pathPrefix + "compartments/private-data"_ns, + zStats.compartmentsPrivateData, + "Extra data attached to each compartment by XPConnect, including " + "its wrapped-js."); + + ZRREPORT_GC_BYTES(pathPrefix + "jit-codes-gc-heap"_ns, zStats.jitCodesGCHeap, + "References to executable code pools used by the JITs."); + + ZRREPORT_GC_BYTES(pathPrefix + "getter-setters-gc-heap"_ns, + zStats.getterSettersGCHeap, + "Information for getter/setter properties."); + + ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/compact"_ns, + zStats.compactPropMapsGCHeap, + "Information about object properties."); + + ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/normal"_ns, + zStats.normalPropMapsGCHeap, + "Information about object properties."); + + ZRREPORT_GC_BYTES(pathPrefix + "property-maps/gc-heap/dict"_ns, + zStats.dictPropMapsGCHeap, + "Information about dictionary mode object properties."); + + ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/children"_ns, + zStats.propMapChildren, "Tables for PropMap children."); + + ZRREPORT_BYTES(pathPrefix + "property-maps/malloc-heap/tables"_ns, + zStats.propMapTables, "HashTables for PropMaps."); + + ZRREPORT_GC_BYTES(pathPrefix + "scopes/gc-heap"_ns, zStats.scopesGCHeap, + "Scope information for scripts."); + + ZRREPORT_BYTES(pathPrefix + "scopes/malloc-heap"_ns, zStats.scopesMallocHeap, + "Arrays of binding names and other binding-related data."); + + ZRREPORT_GC_BYTES(pathPrefix + "regexp-shareds/gc-heap"_ns, + zStats.regExpSharedsGCHeap, "Shared compiled regexp data."); + + ZRREPORT_BYTES(pathPrefix + "regexp-shareds/malloc-heap"_ns, + zStats.regExpSharedsMallocHeap, + "Shared compiled regexp data."); + + ZRREPORT_BYTES(pathPrefix + "regexp-zone"_ns, zStats.regexpZone, + "The regexp zone and regexp data."); + + ZRREPORT_BYTES(pathPrefix + "jit-zone"_ns, zStats.jitZone, "The JIT zone."); + + ZRREPORT_BYTES(pathPrefix + "baseline/optimized-stubs"_ns, + zStats.baselineStubsOptimized, + "The Baseline JIT's optimized IC stubs (excluding code)."); + + ZRREPORT_BYTES(pathPrefix + "script-counts-map"_ns, zStats.scriptCountsMap, + "Profiling-related information for scripts."); + + ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/ion"_ns, zStats.code.ion, + "Code generated by the IonMonkey JIT."); + + ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/baseline"_ns, zStats.code.baseline, + "Code generated by the Baseline JIT."); + + ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/regexp"_ns, zStats.code.regexp, + "Code generated by the regexp JIT."); + + ZRREPORT_NONHEAP_BYTES( + pathPrefix + "code/other"_ns, zStats.code.other, + "Code generated by the JITs for wrappers and trampolines."); + + ZRREPORT_NONHEAP_BYTES(pathPrefix + "code/unused"_ns, zStats.code.unused, + "Memory allocated by one of the JITs to hold code, " + "but which is currently unused."); + + size_t stringsNotableAboutMemoryGCHeap = 0; + size_t stringsNotableAboutMemoryMallocHeap = 0; + +#define MAYBE_INLINE "The characters may be inline or on the malloc heap." +#define MAYBE_OVERALLOCATED \ + "Sometimes over-allocated to simplify string concatenation." + + for (size_t i = 0; i < zStats.notableStrings.length(); i++) { + const JS::NotableStringInfo& info = zStats.notableStrings[i]; + + MOZ_ASSERT(!zStats.isTotals); + + // We don't do notable string detection when anonymizing, because + // there's a good chance its for crash submission, and the memory + // required for notable string detection is high. + MOZ_ASSERT(!anonymize); + + nsDependentCString notableString(info.buffer.get()); + + // Viewing about:memory generates many notable strings which contain + // "string(length=". If we report these as notable, then we'll create + // even more notable strings the next time we open about:memory (unless + // there's a GC in the meantime), and so on ad infinitum. + // + // To avoid cluttering up about:memory like this, we stick notable + // strings which contain "string(length=" into their own bucket. +#define STRING_LENGTH "string(length=" + if (FindInReadable(nsLiteralCString(STRING_LENGTH), notableString)) { + stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1; + stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte; + stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1; + stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte; + continue; + } + + // Escape / to \ before we put notableString into the memory reporter + // path, because we don't want any forward slashes in the string to + // count as path separators. + nsCString escapedString(notableString); + escapedString.ReplaceSubstring("/", "\\"); + + bool truncated = notableString.Length() < info.length; + + nsCString path = + pathPrefix + + nsPrintfCString("strings/" STRING_LENGTH "%zu, copies=%d, \"%s\"%s)/", + info.length, info.numCopies, escapedString.get(), + truncated ? " (truncated)" : ""); + + if (info.gcHeapLatin1 > 0) { + REPORT_GC_BYTES(path + "gc-heap/latin1"_ns, info.gcHeapLatin1, + "Latin1 strings. " MAYBE_INLINE); + } + + if (info.gcHeapTwoByte > 0) { + REPORT_GC_BYTES(path + "gc-heap/two-byte"_ns, info.gcHeapTwoByte, + "TwoByte strings. " MAYBE_INLINE); + } + + if (info.mallocHeapLatin1 > 0) { + REPORT_BYTES(path + "malloc-heap/latin1"_ns, KIND_HEAP, + info.mallocHeapLatin1, + "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); + } + + if (info.mallocHeapTwoByte > 0) { + REPORT_BYTES( + path + "malloc-heap/two-byte"_ns, KIND_HEAP, info.mallocHeapTwoByte, + "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); + } + } + + nsCString nonNotablePath = pathPrefix; + nonNotablePath += (zStats.isTotals || anonymize) + ? "strings/"_ns + : "strings/string(<non-notable strings>)/"_ns; + + if (zStats.stringInfo.gcHeapLatin1 > 0) { + REPORT_GC_BYTES(nonNotablePath + "gc-heap/latin1"_ns, + zStats.stringInfo.gcHeapLatin1, + "Latin1 strings. " MAYBE_INLINE); + } + + if (zStats.stringInfo.gcHeapTwoByte > 0) { + REPORT_GC_BYTES(nonNotablePath + "gc-heap/two-byte"_ns, + zStats.stringInfo.gcHeapTwoByte, + "TwoByte strings. " MAYBE_INLINE); + } + + if (zStats.stringInfo.mallocHeapLatin1 > 0) { + REPORT_BYTES(nonNotablePath + "malloc-heap/latin1"_ns, KIND_HEAP, + zStats.stringInfo.mallocHeapLatin1, + "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); + } + + if (zStats.stringInfo.mallocHeapTwoByte > 0) { + REPORT_BYTES(nonNotablePath + "malloc-heap/two-byte"_ns, KIND_HEAP, + zStats.stringInfo.mallocHeapTwoByte, + "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); + } + + if (stringsNotableAboutMemoryGCHeap > 0) { + MOZ_ASSERT(!zStats.isTotals); + REPORT_GC_BYTES( + pathPrefix + "strings/string(<about-memory>)/gc-heap"_ns, + stringsNotableAboutMemoryGCHeap, + "Strings that contain the characters '" STRING_LENGTH + "', which " + "are probably from about:memory itself." MAYBE_INLINE + " We filter them out rather than display them, because displaying " + "them would create even more such strings every time about:memory " + "is refreshed."); + } + + if (stringsNotableAboutMemoryMallocHeap > 0) { + MOZ_ASSERT(!zStats.isTotals); + REPORT_BYTES( + pathPrefix + "strings/string(<about-memory>)/malloc-heap"_ns, KIND_HEAP, + stringsNotableAboutMemoryMallocHeap, + "Non-inline string characters of strings that contain the " + "characters '" STRING_LENGTH + "', which are probably from " + "about:memory itself. " MAYBE_OVERALLOCATED + " We filter them out rather than display them, because displaying " + "them would create even more such strings every time about:memory " + "is refreshed."); + } + + const JS::ShapeInfo& shapeInfo = zStats.shapeInfo; + if (shapeInfo.shapesGCHeapShared > 0) { + REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/shared"_ns, + shapeInfo.shapesGCHeapShared, "Shared shapes."); + } + + if (shapeInfo.shapesGCHeapDict > 0) { + REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/dict"_ns, + shapeInfo.shapesGCHeapDict, "Shapes in dictionary mode."); + } + + if (shapeInfo.shapesGCHeapBase > 0) { + REPORT_GC_BYTES(pathPrefix + "shapes/gc-heap/base"_ns, + shapeInfo.shapesGCHeapBase, + "Base shapes, which collate data common to many shapes."); + } + + if (shapeInfo.shapesMallocHeapCache > 0) { + REPORT_BYTES(pathPrefix + "shapes/malloc-heap/shape-cache"_ns, KIND_HEAP, + shapeInfo.shapesMallocHeapCache, + "Shape cache hash set for adding properties."); + } + + if (sundriesGCHeap > 0) { + // We deliberately don't use ZRREPORT_GC_BYTES here. + REPORT_GC_BYTES( + pathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, + "The sum of all 'gc-heap' measurements that are too small to be " + "worth showing individually."); + } + + if (sundriesMallocHeap > 0) { + // We deliberately don't use ZRREPORT_BYTES here. + REPORT_BYTES( + pathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, sundriesMallocHeap, + "The sum of all 'malloc-heap' measurements that are too small to " + "be worth showing individually."); + } + + if (sundriesNonHeap > 0) { + // We deliberately don't use ZRREPORT_NONHEAP_BYTES here. + REPORT_BYTES(pathPrefix + "sundries/other-heap"_ns, KIND_NONHEAP, + sundriesNonHeap, + "The sum of non-malloc/gc measurements that are too small to " + "be worth showing individually."); + } + + if (gcTotalOut) { + *gcTotalOut += gcTotal; + } + +#undef STRING_LENGTH +} + +static void ReportClassStats(const ClassInfo& classInfo, const nsACString& path, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t& gcTotal) { + // We deliberately don't use ZRREPORT_BYTES, so that these per-class values + // don't go into sundries. + + if (classInfo.objectsGCHeap > 0) { + REPORT_GC_BYTES(path + "objects/gc-heap"_ns, classInfo.objectsGCHeap, + "Objects, including fixed slots."); + } + + if (classInfo.objectsMallocHeapSlots > 0) { + REPORT_BYTES(path + "objects/malloc-heap/slots"_ns, KIND_HEAP, + classInfo.objectsMallocHeapSlots, "Non-fixed object slots."); + } + + if (classInfo.objectsMallocHeapElementsNormal > 0) { + REPORT_BYTES(path + "objects/malloc-heap/elements/normal"_ns, KIND_HEAP, + classInfo.objectsMallocHeapElementsNormal, + "Normal (non-wasm) indexed elements."); + } + + if (classInfo.objectsMallocHeapElementsAsmJS > 0) { + REPORT_BYTES(path + "objects/malloc-heap/elements/asm.js"_ns, KIND_HEAP, + classInfo.objectsMallocHeapElementsAsmJS, + "asm.js array buffer elements allocated in the malloc heap."); + } + + if (classInfo.objectsMallocHeapGlobalData > 0) { + REPORT_BYTES(path + "objects/malloc-heap/global-data"_ns, KIND_HEAP, + classInfo.objectsMallocHeapGlobalData, + "Data for global objects."); + } + + if (classInfo.objectsMallocHeapGlobalVarNamesSet > 0) { + REPORT_BYTES(path + "objects/malloc-heap/global-varnames-set"_ns, KIND_HEAP, + classInfo.objectsMallocHeapGlobalVarNamesSet, + "Set of global names."); + } + + if (classInfo.objectsMallocHeapMisc > 0) { + REPORT_BYTES(path + "objects/malloc-heap/misc"_ns, KIND_HEAP, + classInfo.objectsMallocHeapMisc, "Miscellaneous object data."); + } + + if (classInfo.objectsNonHeapElementsNormal > 0) { + REPORT_BYTES(path + "objects/non-heap/elements/normal"_ns, KIND_NONHEAP, + classInfo.objectsNonHeapElementsNormal, + "Memory-mapped non-shared array buffer elements."); + } + + if (classInfo.objectsNonHeapElementsShared > 0) { + REPORT_BYTES( + path + "objects/non-heap/elements/shared"_ns, KIND_NONHEAP, + classInfo.objectsNonHeapElementsShared, + "Memory-mapped shared array buffer elements. These elements are " + "shared between one or more runtimes; the reported size is divided " + "by the buffer's refcount."); + } + + // WebAssembly memories are always non-heap-allocated (mmap). We never put + // these under sundries, because (a) in practice they're almost always + // larger than the sundries threshold, and (b) we'd need a third category of + // sundries ("non-heap"), which would be a pain. + if (classInfo.objectsNonHeapElementsWasm > 0) { + REPORT_BYTES(path + "objects/non-heap/elements/wasm"_ns, KIND_NONHEAP, + classInfo.objectsNonHeapElementsWasm, + "wasm/asm.js array buffer elements allocated outside both the " + "malloc heap and the GC heap."); + } + if (classInfo.objectsNonHeapElementsWasmShared > 0) { + REPORT_BYTES( + path + "objects/non-heap/elements/wasm-shared"_ns, KIND_NONHEAP, + classInfo.objectsNonHeapElementsWasmShared, + "wasm/asm.js array buffer elements allocated outside both the " + "malloc heap and the GC heap. These elements are shared between " + "one or more runtimes; the reported size is divided by the " + "buffer's refcount."); + } + + if (classInfo.objectsNonHeapCodeWasm > 0) { + REPORT_BYTES(path + "objects/non-heap/code/wasm"_ns, KIND_NONHEAP, + classInfo.objectsNonHeapCodeWasm, + "AOT-compiled wasm/asm.js code."); + } +} + +static void ReportRealmStats(const JS::RealmStats& realmStats, + const xpc::RealmStatsExtras& extras, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t* gcTotalOut = nullptr) { + static const nsDependentCString addonPrefix("explicit/add-ons/"); + + size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; + nsAutoCString realmJSPathPrefix(extras.jsPathPrefix); + nsAutoCString realmDOMPathPrefix(extras.domPathPrefix); + + MOZ_ASSERT(!gcTotalOut == realmStats.isTotals); + + nsCString nonNotablePath = realmJSPathPrefix; + nonNotablePath += realmStats.isTotals + ? "classes/"_ns + : "classes/class(<non-notable classes>)/"_ns; + + ReportClassStats(realmStats.classInfo, nonNotablePath, handleReport, data, + gcTotal); + + for (size_t i = 0; i < realmStats.notableClasses.length(); i++) { + MOZ_ASSERT(!realmStats.isTotals); + const JS::NotableClassInfo& classInfo = realmStats.notableClasses[i]; + + nsCString classPath = + realmJSPathPrefix + + nsPrintfCString("classes/class(%s)/", classInfo.className_.get()); + + ReportClassStats(classInfo, classPath, handleReport, data, gcTotal); + } + + // Note that we use realmDOMPathPrefix here. This is because we measure + // orphan DOM nodes in the JS reporter, but we want to report them in a "dom" + // sub-tree rather than a "js" sub-tree. + ZRREPORT_BYTES( + realmDOMPathPrefix + "orphan-nodes"_ns, realmStats.objectsPrivate, + "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " + "objects."); + + ZRREPORT_GC_BYTES( + realmJSPathPrefix + "scripts/gc-heap"_ns, realmStats.scriptsGCHeap, + "JSScript instances. There is one per user-defined function in a " + "script, and one for the top-level code in a script."); + + ZRREPORT_BYTES(realmJSPathPrefix + "scripts/malloc-heap/data"_ns, + realmStats.scriptsMallocHeapData, + "Various variable-length tables in JSScripts."); + + ZRREPORT_BYTES(realmJSPathPrefix + "baseline/data"_ns, + realmStats.baselineData, + "The Baseline JIT's compilation data (BaselineScripts)."); + + ZRREPORT_BYTES(realmJSPathPrefix + "baseline/fallback-stubs"_ns, + realmStats.baselineStubsFallback, + "The Baseline JIT's fallback IC stubs (excluding code)."); + + ZRREPORT_BYTES(realmJSPathPrefix + "ion-data"_ns, realmStats.ionData, + "The IonMonkey JIT's compilation data (IonScripts)."); + + ZRREPORT_BYTES(realmJSPathPrefix + "jit-scripts"_ns, realmStats.jitScripts, + "JIT data associated with scripts."); + + ZRREPORT_BYTES(realmJSPathPrefix + "realm-object"_ns, realmStats.realmObject, + "The JS::Realm object itself."); + + ZRREPORT_BYTES( + realmJSPathPrefix + "realm-tables"_ns, realmStats.realmTables, + "Realm-wide tables storing object group information and wasm instances."); + + ZRREPORT_BYTES(realmJSPathPrefix + "inner-views"_ns, + realmStats.innerViewsTable, + "The table for array buffer inner views."); + + ZRREPORT_BYTES( + realmJSPathPrefix + "object-metadata"_ns, realmStats.objectMetadataTable, + "The table used by debugging tools for tracking object metadata"); + + ZRREPORT_BYTES(realmJSPathPrefix + "saved-stacks-set"_ns, + realmStats.savedStacksSet, "The saved stacks set."); + + ZRREPORT_BYTES(realmJSPathPrefix + "non-syntactic-lexical-scopes-table"_ns, + realmStats.nonSyntacticLexicalScopesTable, + "The non-syntactic lexical scopes table."); + + ZRREPORT_BYTES(realmJSPathPrefix + "jit-realm"_ns, realmStats.jitRealm, + "The JIT realm."); + + if (sundriesGCHeap > 0) { + // We deliberately don't use ZRREPORT_GC_BYTES here. + REPORT_GC_BYTES( + realmJSPathPrefix + "sundries/gc-heap"_ns, sundriesGCHeap, + "The sum of all 'gc-heap' measurements that are too small to be " + "worth showing individually."); + } + + if (sundriesMallocHeap > 0) { + // We deliberately don't use ZRREPORT_BYTES here. + REPORT_BYTES( + realmJSPathPrefix + "sundries/malloc-heap"_ns, KIND_HEAP, + sundriesMallocHeap, + "The sum of all 'malloc-heap' measurements that are too small to " + "be worth showing individually."); + } + + if (gcTotalOut) { + *gcTotalOut += gcTotal; + } +} + +static void ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo, + const nsACString& path, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t& rtTotal) { + if (scriptSourceInfo.misc > 0) { + RREPORT_BYTES(path + "misc"_ns, KIND_HEAP, scriptSourceInfo.misc, + "Miscellaneous data relating to JavaScript source code."); + } +} + +void ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, + const nsACString& rtPath, + nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize, + size_t* rtTotalOut) { + size_t gcTotal = 0; + + for (const auto& zStats : rtStats.zoneStatsVector) { + const xpc::ZoneStatsExtras* extras = + static_cast<const xpc::ZoneStatsExtras*>(zStats.extra); + ReportZoneStats(zStats, *extras, handleReport, data, anonymize, &gcTotal); + } + + for (const auto& realmStats : rtStats.realmStatsVector) { + const xpc::RealmStatsExtras* extras = + static_cast<const xpc::RealmStatsExtras*>(realmStats.extra); + + ReportRealmStats(realmStats, *extras, handleReport, data, &gcTotal); + } + + // Report the rtStats.runtime numbers under "runtime/", and compute their + // total for later. + + size_t rtTotal = 0; + + RREPORT_BYTES(rtPath + "runtime/runtime-object"_ns, KIND_HEAP, + rtStats.runtime.object, "The JSRuntime object."); + + RREPORT_BYTES(rtPath + "runtime/atoms-table"_ns, KIND_HEAP, + rtStats.runtime.atomsTable, "The atoms table."); + + RREPORT_BYTES(rtPath + "runtime/atoms-mark-bitmaps"_ns, KIND_HEAP, + rtStats.runtime.atomsMarkBitmaps, + "Mark bitmaps for atoms held by each zone."); + + RREPORT_BYTES(rtPath + "runtime/self-host-stencil"_ns, KIND_HEAP, + rtStats.runtime.selfHostStencil, + "The self-hosting CompilationStencil."); + + RREPORT_BYTES(rtPath + "runtime/contexts"_ns, KIND_HEAP, + rtStats.runtime.contexts, + "JSContext objects and structures that belong to them."); + + RREPORT_BYTES( + rtPath + "runtime/temporary"_ns, KIND_HEAP, rtStats.runtime.temporary, + "Transient data (mostly parse nodes) held by the JSRuntime during " + "compilation."); + + RREPORT_BYTES(rtPath + "runtime/interpreter-stack"_ns, KIND_HEAP, + rtStats.runtime.interpreterStack, "JS interpreter frames."); + + RREPORT_BYTES( + rtPath + "runtime/shared-immutable-strings-cache"_ns, KIND_HEAP, + rtStats.runtime.sharedImmutableStringsCache, + "Immutable strings (such as JS scripts' source text) shared across all " + "JSRuntimes."); + + RREPORT_BYTES(rtPath + "runtime/shared-intl-data"_ns, KIND_HEAP, + rtStats.runtime.sharedIntlData, + "Shared internationalization data."); + + RREPORT_BYTES(rtPath + "runtime/uncompressed-source-cache"_ns, KIND_HEAP, + rtStats.runtime.uncompressedSourceCache, + "The uncompressed source code cache."); + + RREPORT_BYTES(rtPath + "runtime/script-data"_ns, KIND_HEAP, + rtStats.runtime.scriptData, + "The table holding script data shared in the runtime."); + + nsCString nonNotablePath = + rtPath + + nsPrintfCString( + "runtime/script-sources/source(scripts=%d, <non-notable files>)/", + rtStats.runtime.scriptSourceInfo.numScripts); + + ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, nonNotablePath, + handleReport, data, rtTotal); + + for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) { + const JS::NotableScriptSourceInfo& scriptSourceInfo = + rtStats.runtime.notableScriptSources[i]; + + // Escape / to \ before we put the filename into the memory reporter + // path, because we don't want any forward slashes in the string to + // count as path separators. Consumers of memory reporters (e.g. + // about:memory) will convert them back to / after doing path + // splitting. + nsCString escapedFilename; + if (anonymize) { + escapedFilename.AppendPrintf("<anonymized-source-%d>", int(i)); + } else { + nsDependentCString filename(scriptSourceInfo.filename_.get()); + escapedFilename.Append(filename); + escapedFilename.ReplaceSubstring("/", "\\"); + } + + nsCString notablePath = + rtPath + + nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/", + scriptSourceInfo.numScripts, escapedFilename.get()); + + ReportScriptSourceStats(scriptSourceInfo, notablePath, handleReport, data, + rtTotal); + } + + RREPORT_BYTES(rtPath + "runtime/gc/marker"_ns, KIND_HEAP, + rtStats.runtime.gc.marker, "The GC mark stack and gray roots."); + + RREPORT_BYTES(rtPath + "runtime/gc/nursery-committed"_ns, KIND_NONHEAP, + rtStats.runtime.gc.nurseryCommitted, + "Memory being used by the GC's nursery."); + + RREPORT_BYTES( + rtPath + "runtime/gc/nursery-malloced-buffers"_ns, KIND_HEAP, + rtStats.runtime.gc.nurseryMallocedBuffers, + "Out-of-line slots and elements belonging to objects in the nursery."); + + RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/vals"_ns, KIND_HEAP, + rtStats.runtime.gc.storeBufferVals, + "Values in the store buffer."); + + RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/cells"_ns, KIND_HEAP, + rtStats.runtime.gc.storeBufferCells, + "Cells in the store buffer."); + + RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/slots"_ns, KIND_HEAP, + rtStats.runtime.gc.storeBufferSlots, + "Slots in the store buffer."); + + RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/whole-cells"_ns, KIND_HEAP, + rtStats.runtime.gc.storeBufferWholeCells, + "Whole cells in the store buffer."); + + RREPORT_BYTES(rtPath + "runtime/gc/store-buffer/generics"_ns, KIND_HEAP, + rtStats.runtime.gc.storeBufferGenerics, + "Generic things in the store buffer."); + + RREPORT_BYTES(rtPath + "runtime/jit-lazylink"_ns, KIND_HEAP, + rtStats.runtime.jitLazyLink, + "IonMonkey compilations waiting for lazy linking."); + + if (rtTotalOut) { + *rtTotalOut = rtTotal; + } + + // Report GC numbers that don't belong to a realm. + + // We don't want to report decommitted memory in "explicit", so we just + // change the leading "explicit/" to "decommitted/". + nsCString rtPath2(rtPath); + rtPath2.ReplaceLiteral(0, strlen("explicit"), "decommitted"); + + REPORT_GC_BYTES( + rtPath2 + "gc-heap/decommitted-pages"_ns, rtStats.gcHeapDecommittedPages, + "GC arenas in non-empty chunks that is decommitted, i.e. it takes up " + "address space but no physical memory or swap space."); + + REPORT_GC_BYTES( + rtPath + "gc-heap/unused-chunks"_ns, rtStats.gcHeapUnusedChunks, + "Empty GC chunks which will soon be released unless claimed for new " + "allocations."); + + REPORT_GC_BYTES(rtPath + "gc-heap/unused-arenas"_ns, + rtStats.gcHeapUnusedArenas, + "Empty GC arenas within non-empty chunks."); + + REPORT_GC_BYTES(rtPath + "gc-heap/chunk-admin"_ns, rtStats.gcHeapChunkAdmin, + "Bookkeeping information within GC chunks."); + + // gcTotal is the sum of everything we've reported for the GC heap. It + // should equal rtStats.gcHeapChunkTotal. + MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal); +} + +} // namespace xpc + +class JSMainRuntimeRealmsReporter final : public nsIMemoryReporter { + ~JSMainRuntimeRealmsReporter() = default; + + public: + NS_DECL_ISUPPORTS + + struct Data { + int anonymizeID; + js::Vector<nsCString, 0, js::SystemAllocPolicy> paths; + }; + + static void RealmCallback(JSContext* cx, void* vdata, Realm* realm, + const JS::AutoRequireNoGC& nogc) { + // silently ignore OOM errors + Data* data = static_cast<Data*>(vdata); + nsCString path; + GetRealmName(realm, path, &data->anonymizeID, /* replaceSlashes = */ true); + path.Insert(js::IsSystemRealm(realm) ? "js-main-runtime-realms/system/"_ns + : "js-main-runtime-realms/user/"_ns, + 0); + mozilla::Unused << data->paths.append(path); + } + + NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize) override { + // First we collect the realm paths. Then we report them. Doing + // the two steps interleaved is a bad idea, because calling + // |handleReport| from within RealmCallback() leads to all manner + // of assertions. + + Data d; + d.anonymizeID = anonymize ? 1 : 0; + JS::IterateRealms(XPCJSContext::Get()->Context(), &d, RealmCallback); + + for (auto& path : d.paths) { + REPORT(nsCString(path), KIND_OTHER, UNITS_COUNT, 1, + "A live realm in the main JSRuntime."); + } + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(JSMainRuntimeRealmsReporter, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf) + +namespace xpc { + +class OrphanReporter : public JS::ObjectPrivateVisitor { + public: + explicit OrphanReporter(GetISupportsFun aGetISupports) + : JS::ObjectPrivateVisitor(aGetISupports), mState(OrphanMallocSizeOf) {} + + virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override { + nsCOMPtr<nsINode> node = do_QueryInterface(aSupports); + if (!node || node->IsInComposedDoc()) { + return 0; + } + + // This is an orphan node. If we haven't already handled the sub-tree that + // this node belongs to, measure the sub-tree's size and then record its + // root so we don't measure it again. + nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot(); + if (!orphanTree || mState.HaveSeenPtr(orphanTree.get())) { + return 0; + } + + nsWindowSizes sizes(mState); + mozilla::dom::Document::AddSizeOfNodeTree(*orphanTree, sizes); + + // We combine the node size with nsStyleSizes here. It's not ideal, but it's + // hard to get the style structs measurements out to nsWindowMemoryReporter. + // Also, we drop mServoData in UnbindFromTree(), so in theory any + // non-in-tree element won't have any style data to measure. + // + // FIXME(emilio): We should ideally not do this, since ShadowRoots keep + // their StyleSheets alive even when detached from a document, and those + // could be significant in theory. + return sizes.getTotalSize(); + } + + private: + SizeOfState mState; +}; + +#ifdef DEBUG +static bool StartsWithExplicit(nsACString& s) { + return StringBeginsWith(s, "explicit/"_ns); +} +#endif + +class XPCJSRuntimeStats : public JS::RuntimeStats { + WindowPaths* mWindowPaths; + WindowPaths* mTopWindowPaths; + int mAnonymizeID; + + public: + XPCJSRuntimeStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths, + bool anonymize) + : JS::RuntimeStats(JSMallocSizeOf), + mWindowPaths(windowPaths), + mTopWindowPaths(topWindowPaths), + mAnonymizeID(anonymize ? 1 : 0) {} + + ~XPCJSRuntimeStats() { + for (size_t i = 0; i != realmStatsVector.length(); ++i) { + delete static_cast<xpc::RealmStatsExtras*>(realmStatsVector[i].extra); + } + + for (size_t i = 0; i != zoneStatsVector.length(); ++i) { + delete static_cast<xpc::ZoneStatsExtras*>(zoneStatsVector[i].extra); + } + } + + virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats, + const JS::AutoRequireNoGC& nogc) override { + xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras; + extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + + // Get some global in this zone. + Rooted<Realm*> realm(dom::RootingCx(), js::GetAnyRealmInZone(zone)); + if (realm) { + RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); + if (global) { + RefPtr<nsGlobalWindowInner> window; + if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { + // The global is a |window| object. Use the path prefix that + // we should have already created for it. + if (mTopWindowPaths->Get(window->WindowID(), &extras->pathPrefix)) { + extras->pathPrefix.AppendLiteral("/js-"); + } + } + } + } + + extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)zone); + + MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix)); + + zStats->extra = extras; + } + + virtual void initExtraRealmStats(Realm* realm, JS::RealmStats* realmStats, + const JS::AutoRequireNoGC& nogc) override { + xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras; + nsCString rName; + GetRealmName(realm, rName, &mAnonymizeID, /* replaceSlashes = */ true); + + // Get the realm's global. + bool needZone = true; + RootedObject global(dom::RootingCx(), JS::GetRealmGlobalOrNull(realm)); + if (global) { + RefPtr<nsGlobalWindowInner> window; + if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Window, global, window))) { + // The global is a |window| object. Use the path prefix that + // we should have already created for it. + if (mWindowPaths->Get(window->WindowID(), &extras->jsPathPrefix)) { + extras->domPathPrefix.Assign(extras->jsPathPrefix); + extras->domPathPrefix.AppendLiteral("/dom/"); + extras->jsPathPrefix.AppendLiteral("/js-"); + needZone = false; + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral( + "explicit/dom/unknown-window-global?!/"); + } + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral( + "explicit/dom/non-window-global?!/"); + } + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/"); + } + + if (needZone) { + extras->jsPathPrefix += + nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(realm)); + } + + extras->jsPathPrefix += "realm("_ns + rName + ")/"_ns; + + // extras->jsPathPrefix is used for almost all the realm-specific + // reports. At this point it has the form + // "<something>realm(<rname>)/". + // + // extras->domPathPrefix is used for DOM orphan nodes, which are + // counted by the JS reporter but reported as part of the DOM + // measurements. At this point it has the form "<something>/dom/" if + // this realm belongs to an nsGlobalWindow, and + // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't + // be used, because non-nsGlobalWindow realms shouldn't have + // orphan DOM nodes). + + MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix)); + MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix)); + + realmStats->extra = extras; + } +}; + +void JSReporter::CollectReports(WindowPaths* windowPaths, + WindowPaths* topWindowPaths, + nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize) { + XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance(); + + // In the first step we get all the stats and stash them in a local + // data structure. In the second step we pass all the stashed stats to + // the callback. Separating these steps is important because the + // callback may be a JS function, and executing JS while getting these + // stats seems like a bad idea. + + XPCJSRuntimeStats rtStats(windowPaths, topWindowPaths, anonymize); + OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); + JSContext* cx = XPCJSContext::Get()->Context(); + if (!JS::CollectRuntimeStats(cx, &rtStats, &orphanReporter, anonymize)) { + return; + } + + // Collect JS stats not associated with a Runtime such as helper threads or + // global tracelogger data. We do this here in JSReporter::CollectReports + // as this is used for the main Runtime in process. + JS::GlobalStats gStats(JSMallocSizeOf); + if (!JS::CollectGlobalStats(&gStats)) { + return; + } + + size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf); + + size_t wrappedJSSize = + xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf); + + XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf); + XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(cx, &sizeInfo); + + mozJSModuleLoader* loader = mozJSModuleLoader::Get(); + size_t jsModuleLoaderSize = + loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0; + mozJSModuleLoader* devToolsLoader = mozJSModuleLoader::GetDevToolsLoader(); + size_t jsDevToolsModuleLoaderSize = + devToolsLoader ? devToolsLoader->SizeOfIncludingThis(JSMallocSizeOf) : 0; + + // This is the second step (see above). First we report stuff in the + // "explicit" tree, then we report other stuff. + + size_t rtTotal = 0; + xpc::ReportJSRuntimeExplicitTreeStats(rtStats, "explicit/js-non-window/"_ns, + handleReport, data, anonymize, + &rtTotal); + + // Report the sums of the realm numbers. + xpc::RealmStatsExtras realmExtrasTotal; + realmExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/realms/"); + realmExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/"); + ReportRealmStats(rtStats.realmTotals, realmExtrasTotal, handleReport, data); + + xpc::ZoneStatsExtras zExtrasTotal; + zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/"); + ReportZoneStats(rtStats.zTotals, zExtrasTotal, handleReport, data, anonymize); + + // Report the sum of the runtime/ numbers. + REPORT_BYTES( + "js-main-runtime/runtime"_ns, KIND_OTHER, rtTotal, + "The sum of all measurements under 'explicit/js-non-window/runtime/'."); + + // Report the number of HelperThread + + REPORT("js-helper-threads/idle"_ns, KIND_OTHER, UNITS_COUNT, + gStats.helperThread.idleThreadCount, + "The current number of idle JS HelperThreads."); + + REPORT( + "js-helper-threads/active"_ns, KIND_OTHER, UNITS_COUNT, + gStats.helperThread.activeThreadCount, + "The current number of active JS HelperThreads. Memory held by these is" + " not reported."); + + // Report the numbers for memory used by wasm Runtime state. + REPORT_BYTES("wasm-runtime"_ns, KIND_OTHER, rtStats.runtime.wasmRuntime, + "The memory used for wasm runtime bookkeeping."); + + // Although wasm guard pages aren't committed in memory they can be very + // large and contribute greatly to vsize and so are worth reporting. + if (rtStats.runtime.wasmGuardPages > 0) { + REPORT_BYTES( + "wasm-guard-pages"_ns, KIND_OTHER, rtStats.runtime.wasmGuardPages, + "Guard pages mapped after the end of wasm memories, reserved for " + "optimization tricks, but not committed and thus never contributing" + " to RSS, only vsize."); + } + + // Report the numbers for memory outside of realms. + + REPORT_BYTES("js-main-runtime/gc-heap/unused-chunks"_ns, KIND_OTHER, + rtStats.gcHeapUnusedChunks, + "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); + + REPORT_BYTES("js-main-runtime/gc-heap/unused-arenas"_ns, KIND_OTHER, + rtStats.gcHeapUnusedArenas, + "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); + + REPORT_BYTES("js-main-runtime/gc-heap/chunk-admin"_ns, KIND_OTHER, + rtStats.gcHeapChunkAdmin, + "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); + + // Report a breakdown of the committed GC space. + + REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/chunks"_ns, KIND_OTHER, + rtStats.gcHeapUnusedChunks, + "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); + + REPORT_BYTES("js-main-runtime-gc-heap-committed/unused/arenas"_ns, KIND_OTHER, + rtStats.gcHeapUnusedArenas, + "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/objects"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.object, + "Unused object cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/strings"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.string, + "Unused string cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/symbols"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol, + "Unused symbol cells within non-empty arenas."); + + REPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/shapes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.shape, + "Unused shape cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape, + "Unused base shape cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/getter-setters"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.getterSetter, + "Unused getter-setter cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/property-maps"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.propMap, + "Unused property map cells within non-empty arenas."); + + REPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/scopes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.scope, + "Unused scope cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/scripts"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.script, + "Unused script cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, + "Unused jitcode cells within non-empty arenas."); + + REPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/unused/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.regExpShared, + "Unused regexpshared cells within non-empty arenas."); + + REPORT_BYTES("js-main-runtime-gc-heap-committed/used/chunk-admin"_ns, + KIND_OTHER, rtStats.gcHeapChunkAdmin, + "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); + + REPORT_BYTES("js-main-runtime-gc-heap-committed/used/arena-admin"_ns, + KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, + "The same as 'js-main-runtime/zones/gc-heap-arena-admin'."); + + size_t gcThingTotal = 0; + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/objects"), + KIND_OTHER, rtStats.realmTotals.classInfo.objectsGCHeap, + "Used object cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/strings"), + KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(), + "Used string cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/symbols"), + KIND_OTHER, rtStats.zTotals.symbolsGCHeap, + "Used symbol cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/shapes"), + KIND_OTHER, + rtStats.zTotals.shapeInfo.shapesGCHeapShared + + rtStats.zTotals.shapeInfo.shapesGCHeapDict, + "Used shape cells."); + + MREPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"), + KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase, + "Used base shape cells."); + + MREPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/getter-setters"), + KIND_OTHER, rtStats.zTotals.getterSettersGCHeap, + "Used getter/setter cells."); + + MREPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/property-maps"), + KIND_OTHER, + rtStats.zTotals.dictPropMapsGCHeap + + rtStats.zTotals.compactPropMapsGCHeap + + rtStats.zTotals.normalPropMapsGCHeap, + "Used property map cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/scopes"), + KIND_OTHER, rtStats.zTotals.scopesGCHeap, "Used scope cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/scripts"), + KIND_OTHER, rtStats.realmTotals.scriptsGCHeap, + "Used script cells."); + + MREPORT_BYTES(nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/jitcode"), + KIND_OTHER, rtStats.zTotals.jitCodesGCHeap, + "Used jitcode cells."); + + MREPORT_BYTES( + nsLiteralCString( + "js-main-runtime-gc-heap-committed/used/gc-things/regexp-shareds"), + KIND_OTHER, rtStats.zTotals.regExpSharedsGCHeap, + "Used regexpshared cells."); + + MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings); + (void)gcThingTotal; + + // Report xpconnect. + + REPORT_BYTES("explicit/xpconnect/runtime"_ns, KIND_HEAP, xpcJSRuntimeSize, + "The XPConnect runtime."); + + REPORT_BYTES("explicit/xpconnect/wrappedjs"_ns, KIND_HEAP, wrappedJSSize, + "Wrappers used to implement XPIDL interfaces with JS."); + + REPORT_BYTES("explicit/xpconnect/scopes"_ns, KIND_HEAP, + sizeInfo.mScopeAndMapSize, "XPConnect scopes."); + + REPORT_BYTES("explicit/xpconnect/proto-iface-cache"_ns, KIND_HEAP, + sizeInfo.mProtoAndIfaceCacheSize, + "Prototype and interface binding caches."); + + REPORT_BYTES("explicit/xpconnect/js-module-loader"_ns, KIND_HEAP, + jsModuleLoaderSize, "XPConnect's JS module loader."); + REPORT_BYTES("explicit/xpconnect/js-devtools-module-loader"_ns, KIND_HEAP, + jsDevToolsModuleLoaderSize, "DevTools's JS module loader."); + + // Report HelperThreadState. + + REPORT_BYTES("explicit/js-non-window/helper-thread/heap-other"_ns, KIND_HEAP, + gStats.helperThread.stateData, + "Memory used by HelperThreadState."); + + REPORT_BYTES("explicit/js-non-window/helper-thread/parse-task"_ns, KIND_HEAP, + gStats.helperThread.parseTask, + "The memory used by ParseTasks waiting in HelperThreadState."); + + REPORT_BYTES( + "explicit/js-non-window/helper-thread/ion-compile-task"_ns, KIND_HEAP, + gStats.helperThread.ionCompileTask, + "The memory used by IonCompileTasks waiting in HelperThreadState."); + + REPORT_BYTES( + "explicit/js-non-window/helper-thread/wasm-compile"_ns, KIND_HEAP, + gStats.helperThread.wasmCompile, + "The memory used by Wasm compilations waiting in HelperThreadState."); + + REPORT_BYTES("explicit/js-non-window/helper-thread/contexts"_ns, KIND_HEAP, + gStats.helperThread.contexts, + "The memory used by the JSContexts in HelperThreadState."); +} + +static nsresult JSSizeOfTab(JSObject* objArg, size_t* jsObjectsSize, + size_t* jsStringsSize, size_t* jsPrivateSize, + size_t* jsOtherSize) { + JSContext* cx = XPCJSContext::Get()->Context(); + JS::RootedObject obj(cx, objArg); + + TabSizes sizes; + OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); + NS_ENSURE_TRUE( + JS::AddSizeOfTab(cx, obj, moz_malloc_size_of, &orphanReporter, &sizes), + NS_ERROR_OUT_OF_MEMORY); + + *jsObjectsSize = sizes.objects_; + *jsStringsSize = sizes.strings_; + *jsPrivateSize = sizes.private_; + *jsOtherSize = sizes.other_; + return NS_OK; +} + +} // namespace xpc + +static void AccumulateTelemetryCallback(JSMetric id, uint32_t sample) { + // clang-format off + switch (id) { +#define CASE_ACCUMULATE(NAME, _) \ + case JSMetric::NAME: \ + Telemetry::Accumulate(Telemetry::NAME, sample); \ + break; + + FOR_EACH_JS_METRIC(CASE_ACCUMULATE) +#undef CASE_ACCUMULATE + + default: + MOZ_CRASH("Bad metric id"); + } + // clang-format on +} + +static void SetUseCounterCallback(JSObject* obj, JSUseCounter counter) { + switch (counter) { + case JSUseCounter::ASMJS: + SetUseCounter(obj, eUseCounter_custom_JS_asmjs); + break; + case JSUseCounter::WASM: + SetUseCounter(obj, eUseCounter_custom_JS_wasm); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected JSUseCounter id"); + } +} + +static void GetRealmNameCallback(JSContext* cx, Realm* realm, char* buf, + size_t bufsize, + const JS::AutoRequireNoGC& nogc) { + nsCString name; + // This is called via the JSAPI and isn't involved in memory reporting, so + // we don't need to anonymize realm names. + int anonymizeID = 0; + GetRealmName(realm, name, &anonymizeID, /* replaceSlashes = */ false); + if (name.Length() >= bufsize) { + name.Truncate(bufsize - 1); + } + memcpy(buf, name.get(), name.Length() + 1); +} + +static void DestroyRealm(JS::GCContext* gcx, JS::Realm* realm) { + // Get the current compartment private into an AutoPtr (which will do the + // cleanup for us), and null out the private field. + mozilla::UniquePtr<RealmPrivate> priv(RealmPrivate::Get(realm)); + JS::SetRealmPrivate(realm, nullptr); +} + +static bool PreserveWrapper(JSContext* cx, JS::Handle<JSObject*> obj) { + MOZ_ASSERT(cx); + MOZ_ASSERT(obj); + MOZ_ASSERT(mozilla::dom::IsDOMObject(obj)); + + if (!mozilla::dom::TryPreserveWrapper(obj)) { + return false; + } + + MOZ_ASSERT(!mozilla::dom::HasReleasedWrapper(obj), + "There should be no released wrapper since we just preserved it"); + + return true; +} + +static nsresult ReadSourceFromFilename(JSContext* cx, const char* filename, + char16_t** twoByteSource, + char** utf8Source, size_t* len) { + MOZ_ASSERT(*len == 0); + MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), + "must be called requesting only one of UTF-8 or UTF-16 source"); + MOZ_ASSERT_IF(twoByteSource, !*twoByteSource); + MOZ_ASSERT_IF(utf8Source, !*utf8Source); + + nsresult rv; + + // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with + // the filename of its caller. Axe that if present. + const char* arrow; + while ((arrow = strstr(filename, " -> "))) { + filename = arrow + strlen(" -> "); + } + + // Get the URI. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), filename); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> scriptChannel; + rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + // Only allow local reading. + nsCOMPtr<nsIURI> actualUri; + rv = scriptChannel->GetURI(getter_AddRefs(actualUri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString scheme; + rv = actualUri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) { + return NS_OK; + } + + // Explicitly set the content type so that we don't load the + // exthandler to guess it. + scriptChannel->SetContentType("text/plain"_ns); + + nsCOMPtr<nsIInputStream> scriptStream; + rv = scriptChannel->Open(getter_AddRefs(scriptStream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t rawLen; + rv = scriptStream->Available(&rawLen); + NS_ENSURE_SUCCESS(rv, rv); + if (!rawLen) { + return NS_ERROR_FAILURE; + } + + // Technically, this should be SIZE_MAX, but we don't run on machines + // where that would be less than UINT32_MAX, and the latter is already + // well beyond a reasonable limit. + if (rawLen > UINT32_MAX) { + return NS_ERROR_FILE_TOO_BIG; + } + + // Allocate a buffer the size of the file to initially fill with the UTF-8 + // contents of the file. Use the JS allocator so that if UTF-8 source was + // requested, we can return this memory directly. + JS::UniqueChars buf(js_pod_malloc<char>(rawLen)); + if (!buf) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* ptr = buf.get(); + char* end = ptr + rawLen; + while (ptr < end) { + uint32_t bytesRead; + rv = scriptStream->Read(ptr, PointerRangeSize(ptr, end), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF"); + ptr += bytesRead; + } + + if (utf8Source) { + // |buf| is already UTF-8, so we can directly return it. + *len = rawLen; + *utf8Source = buf.release(); + } else { + MOZ_ASSERT(twoByteSource != nullptr); + + // |buf| can't be directly returned -- convert it to UTF-16. + + // On success this overwrites |*twoByteSource| and |*len|. + rv = ScriptLoader::ConvertToUTF16( + scriptChannel, reinterpret_cast<const unsigned char*>(buf.get()), + rawLen, u"UTF-8"_ns, nullptr, *twoByteSource, *len); + NS_ENSURE_SUCCESS(rv, rv); + + if (!*twoByteSource) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +// The JS engine calls this object's 'load' member function when it needs +// the source for a chrome JS function. See the comment in the XPCJSRuntime +// constructor. +class XPCJSSourceHook : public js::SourceHook { + bool load(JSContext* cx, const char* filename, char16_t** twoByteSource, + char** utf8Source, size_t* length) override { + MOZ_ASSERT((twoByteSource != nullptr) != (utf8Source != nullptr), + "must be called requesting only one of UTF-8 or UTF-16 source"); + + *length = 0; + if (twoByteSource) { + *twoByteSource = nullptr; + } else { + *utf8Source = nullptr; + } + + if (!nsContentUtils::IsSystemCaller(cx)) { + return true; + } + + if (!filename) { + return true; + } + + nsresult rv = + ReadSourceFromFilename(cx, filename, twoByteSource, utf8Source, length); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + return true; + } +}; + +static const JSWrapObjectCallbacks WrapObjectCallbacks = { + xpc::WrapperFactory::Rewrap, xpc::WrapperFactory::PrepareForWrapping}; + +XPCJSRuntime::XPCJSRuntime(JSContext* aCx) + : CycleCollectedJSRuntime(aCx), + mWrappedJSMap(mozilla::MakeUnique<JSObject2WrappedJSMap>()), + mIID2NativeInterfaceMap(mozilla::MakeUnique<IID2NativeInterfaceMap>()), + mClassInfo2NativeSetMap(mozilla::MakeUnique<ClassInfo2NativeSetMap>()), + mNativeSetMap(mozilla::MakeUnique<NativeSetMap>()), + mWrappedNativeScopes(), + mGCIsRunning(false), + mNativesToReleaseArray(), + mDoingFinalization(false), + mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) { + MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime); +} + +/* static */ +XPCJSRuntime* XPCJSRuntime::Get() { return nsXPConnect::GetRuntimeInstance(); } + +// Subclass of JS::ubi::Base for DOM reflector objects for the JS::ubi::Node +// memory analysis framework; see js/public/UbiNode.h. In +// XPCJSRuntime::Initialize, we register the ConstructUbiNode function as a hook +// with the SpiderMonkey runtime for it to use to construct ubi::Nodes of this +// class for JSObjects whose class has the JSCLASS_IS_DOMJSCLASS flag set. +// ReflectorNode specializes Concrete<JSObject> for DOM reflector nodes, +// reporting the edge from the JSObject to the nsINode it represents, in +// addition to the usual edges departing any normal JSObject. +namespace JS { +namespace ubi { +class ReflectorNode : public Concrete<JSObject> { + protected: + explicit ReflectorNode(JSObject* ptr) : Concrete<JSObject>(ptr) {} + + public: + static void construct(void* storage, JSObject* ptr) { + new (storage) ReflectorNode(ptr); + } + js::UniquePtr<JS::ubi::EdgeRange> edges(JSContext* cx, + bool wantNames) const override; +}; + +js::UniquePtr<EdgeRange> ReflectorNode::edges(JSContext* cx, + bool wantNames) const { + js::UniquePtr<SimpleEdgeRange> range(static_cast<SimpleEdgeRange*>( + Concrete<JSObject>::edges(cx, wantNames).release())); + if (!range) { + return nullptr; + } + // UNWRAP_NON_WRAPPER_OBJECT assumes the object is completely initialized, + // but ours may not be. Luckily, UnwrapDOMObjectToISupports checks for the + // uninitialized case (and returns null if uninitialized), so we can use that + // to guard against uninitialized objects. + nsISupports* supp = UnwrapDOMObjectToISupports(&get()); + if (supp) { + JS::AutoSuppressGCAnalysis nogc; // bug 1582326 + + nsINode* node; + // UnwrapDOMObjectToISupports can only return non-null if its argument is + // an actual DOM object, not a cross-compartment wrapper. + if (NS_SUCCEEDED(UNWRAP_NON_WRAPPER_OBJECT(Node, &get(), node))) { + char16_t* edgeName = nullptr; + if (wantNames) { + edgeName = NS_xstrdup(u"Reflected Node"); + } + if (!range->addEdge(Edge(edgeName, node))) { + return nullptr; + } + } + } + return js::UniquePtr<EdgeRange>(range.release()); +} + +} // Namespace ubi +} // Namespace JS + +void ConstructUbiNode(void* storage, JSObject* ptr) { + JS::ubi::ReflectorNode::construct(storage, ptr); +} + +void XPCJSRuntime::Initialize(JSContext* cx) { + mLoaderGlobal.init(cx, nullptr); + + // these jsids filled in later when we have a JSContext to work with. + mStrIDs[0] = JS::PropertyKey::Void(); + + nsScriptSecurityManager::GetScriptSecurityManager()->InitJSCallbacks(cx); + + // Unconstrain the runtime's threshold on nominal heap size, to avoid + // triggering GC too often if operating continuously near an arbitrary + // finite threshold (0xffffffff is infinity for uint32_t parameters). + // This leaves the maximum-JS_malloc-bytes threshold still in effect + // to cause period, and we hope hygienic, last-ditch GCs from within + // the GC's allocator. + JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff); + + JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback); + JS_SetSizeOfIncludingThisCompartmentCallback( + cx, CompartmentSizeOfIncludingThisCallback); + JS::SetDestroyRealmCallback(cx, DestroyRealm); + JS::SetRealmNameCallback(cx, GetRealmNameCallback); + mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback); + mPrevDoCycleCollectionCallback = + JS::SetDoCycleCollectionCallback(cx, DoCycleCollectionCallback); + JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr); + JS_AddWeakPointerZonesCallback(cx, WeakPointerZonesCallback, this); + JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, + this); + JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); + if (XRE_IsE10sParentProcess()) { + JS::SetFilenameValidationCallback( + nsContentSecurityUtils::ValidateScriptFilename); + } + js::SetPreserveWrapperCallbacks(cx, PreserveWrapper, HasReleasedWrapper); + JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals); + JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback); + JS_SetSetUseCounterCallback(cx, SetUseCounterCallback); + + js::SetWindowProxyClass(cx, &OuterWindowProxyClass); + + JS::SetXrayJitInfo(&gXrayJitInfo); + JS::SetProcessLargeAllocationFailureCallback( + OnLargeAllocationFailureCallback); + + // The WasmAltDataType is build by the JS engine from the build id. + JS::SetProcessBuildIdOp(GetBuildId); + FetchUtil::InitWasmAltDataType(); + + // The JS engine needs to keep the source code around in order to implement + // Function.prototype.toSource(). It'd be nice to not have to do this for + // chrome code and simply stub out requests for source on it. Life is not so + // easy, unfortunately. Nobody relies on chrome toSource() working in core + // browser code, but chrome tests use it. The worst offenders are addons, + // which like to monkeypatch chrome functions by calling toSource() on them + // and using regular expressions to modify them. We avoid keeping most browser + // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when + // compiling some chrome code. This causes the JS engine not save the source + // code in memory. When the JS engine is asked to provide the source for a + // function compiled with LAZY_SOURCE, it calls SourceHook to load it. + /// + // Note we do have to retain the source code in memory for scripts compiled in + // isRunOnce mode and compiled function bodies (from + // JS::CompileFunction). In practice, this means content scripts and event + // handlers. + mozilla::UniquePtr<XPCJSSourceHook> hook(new XPCJSSourceHook); + js::SetSourceHook(cx, std::move(hook)); + + // Register memory reporters and distinguished amount functions. + RegisterStrongMemoryReporter(new JSMainRuntimeRealmsReporter()); + RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter()); + RegisterJSMainRuntimeGCHeapDistinguishedAmount( + JSMainRuntimeGCHeapDistinguishedAmount); + RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount( + JSMainRuntimeTemporaryPeakDistinguishedAmount); + RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount( + JSMainRuntimeCompartmentsSystemDistinguishedAmount); + RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount( + JSMainRuntimeCompartmentsUserDistinguishedAmount); + RegisterJSMainRuntimeRealmsSystemDistinguishedAmount( + JSMainRuntimeRealmsSystemDistinguishedAmount); + RegisterJSMainRuntimeRealmsUserDistinguishedAmount( + JSMainRuntimeRealmsUserDistinguishedAmount); + mozilla::RegisterJSSizeOfTab(JSSizeOfTab); + + // Set the callback for reporting memory to ubi::Node. + JS::ubi::SetConstructUbiNodeForDOMObjectCallback(cx, &ConstructUbiNode); + + xpc_LocalizeRuntime(JS_GetRuntime(cx)); +} + +bool XPCJSRuntime::InitializeStrings(JSContext* cx) { + // if it is our first context then we need to generate our string ids + if (mStrIDs[0].isVoid()) { + RootedString str(cx); + for (unsigned i = 0; i < XPCJSContext::IDX_TOTAL_COUNT; i++) { + str = JS_AtomizeAndPinString(cx, mStrings[i]); + if (!str) { + mStrIDs[0] = JS::PropertyKey::Void(); + return false; + } + mStrIDs[i] = PropertyKey::fromPinnedString(str); + } + + if (!mozilla::dom::DefineStaticJSVals(cx)) { + return false; + } + } + + return true; +} + +bool XPCJSRuntime::DescribeCustomObjects(JSObject* obj, const JSClass* clasp, + char (&name)[72]) const { + if (clasp != &XPC_WN_Proto_JSClass) { + return false; + } + + XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); + // Nothing here can GC. The analysis would otherwise think that ~nsCOMPtr + // could GC, but that's only possible if nsIXPCScriptable::GetJSClass() + // somehow released a reference to the nsIXPCScriptable, which isn't going to + // happen. + JS::AutoSuppressGCAnalysis nogc; + nsCOMPtr<nsIXPCScriptable> scr = p->GetScriptable(); + if (!scr) { + return false; + } + + SprintfLiteral(name, "JS Object (%s - %s)", clasp->name, + scr->GetJSClass()->name); + return true; +} + +bool XPCJSRuntime::NoteCustomGCThingXPCOMChildren( + const JSClass* clasp, JSObject* obj, + nsCycleCollectionTraversalCallback& cb) const { + if (clasp != &XPC_WN_Tearoff_JSClass) { + return false; + } + + // A tearoff holds a strong reference to its native object + // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative + // will be held alive through tearoff's XPC_WN_TEAROFF_FLAT_OBJECT_SLOT, + // which points to the XPCWrappedNative's mFlatJSObject. + XPCWrappedNativeTearOff* to = XPCWrappedNativeTearOff::Get(obj); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( + cb, "XPCWrappedNativeTearOff::Get(obj)->mNative"); + cb.NoteXPCOMChild(to->GetNative()); + return true; +} + +/***************************************************************************/ + +void XPCJSRuntime::DebugDump(int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCJSRuntime @ %p", this)); + XPC_LOG_INDENT(); + + // iterate wrappers... + XPC_LOG_ALWAYS(("mWrappedJSMap @ %p with %d wrappers(s)", mWrappedJSMap.get(), + mWrappedJSMap->Count())); + if (depth && mWrappedJSMap->Count()) { + XPC_LOG_INDENT(); + mWrappedJSMap->Dump(depth); + XPC_LOG_OUTDENT(); + } + + XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %p with %d interface(s)", + mIID2NativeInterfaceMap.get(), + mIID2NativeInterfaceMap->Count())); + + XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %p with %d sets(s)", + mClassInfo2NativeSetMap.get(), + mClassInfo2NativeSetMap->Count())); + + XPC_LOG_ALWAYS(("mNativeSetMap @ %p with %d sets(s)", mNativeSetMap.get(), + mNativeSetMap->Count())); + + // iterate sets... + if (depth && mNativeSetMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mNativeSetMap->Iter(); !i.done(); i.next()) { + i.get()->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + + XPC_LOG_OUTDENT(); +#endif +} + +/***************************************************************************/ + +void XPCJSRuntime::AddGCCallback(xpcGCCallback cb) { + MOZ_ASSERT(cb, "null callback"); + extraGCCallbacks.AppendElement(cb); +} + +void XPCJSRuntime::RemoveGCCallback(xpcGCCallback cb) { + MOZ_ASSERT(cb, "null callback"); + bool found = extraGCCallbacks.RemoveElement(cb); + if (!found) { + NS_ERROR("Removing a callback which was never added."); + } +} + +JSObject* XPCJSRuntime::GetUAWidgetScope(JSContext* cx, + nsIPrincipal* principal) { + MOZ_ASSERT(!principal->IsSystemPrincipal(), "Running UA Widget in chrome"); + + RootedObject scope(cx); + do { + RefPtr<BasePrincipal> key = BasePrincipal::Cast(principal); + if (Principal2JSObjectMap::Ptr p = mUAWidgetScopeMap.lookup(key)) { + scope = p->value(); + break; // Need ~RefPtr to run, and potentially GC, before returning. + } + + SandboxOptions options; + options.sandboxName.AssignLiteral("UA Widget Scope"); + options.wantXrays = false; + options.wantComponents = false; + options.isUAWidgetScope = true; + + // Use an ExpandedPrincipal to create asymmetric security. + MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal)); + nsTArray<nsCOMPtr<nsIPrincipal>> principalAsArray{principal}; + RefPtr<ExpandedPrincipal> ep = ExpandedPrincipal::Create( + principalAsArray, principal->OriginAttributesRef()); + + // Create the sandbox. + RootedValue v(cx); + nsresult rv = CreateSandboxObject( + cx, &v, static_cast<nsIExpandedPrincipal*>(ep), options); + NS_ENSURE_SUCCESS(rv, nullptr); + scope = &v.toObject(); + + JSObject* unwrapped = js::UncheckedUnwrap(scope); + MOZ_ASSERT(xpc::IsInUAWidgetScope(unwrapped)); + + MOZ_ALWAYS_TRUE(mUAWidgetScopeMap.putNew(key, unwrapped)); + } while (false); + + return scope; +} + +JSObject* XPCJSRuntime::UnprivilegedJunkScope(const mozilla::fallible_t&) { + if (!mUnprivilegedJunkScope) { + dom::AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + SandboxOptions options; + options.sandboxName.AssignLiteral("XPConnect Junk Compartment"); + options.invisibleToDebugger = true; + + RootedValue sandbox(cx); + nsresult rv = CreateSandboxObject(cx, &sandbox, nullptr, options); + NS_ENSURE_SUCCESS(rv, nullptr); + + mUnprivilegedJunkScope = + SandboxPrivate::GetPrivate(sandbox.toObjectOrNull()); + } + MOZ_ASSERT(mUnprivilegedJunkScope->GetWrapper(), + "Wrapper should have same lifetime as weak reference"); + return mUnprivilegedJunkScope->GetWrapper(); +} + +JSObject* XPCJSRuntime::UnprivilegedJunkScope() { + JSObject* scope = UnprivilegedJunkScope(fallible); + MOZ_RELEASE_ASSERT(scope); + return scope; +} + +bool XPCJSRuntime::IsUnprivilegedJunkScope(JSObject* obj) { + return mUnprivilegedJunkScope && obj == mUnprivilegedJunkScope->GetWrapper(); +} + +void XPCJSRuntime::DeleteSingletonScopes() { + // We're pretty late in shutdown, so we call ReleaseWrapper on the scopes. + // This way the GC can collect them immediately, and we don't rely on the CC + // to clean up. + if (RefPtr<SandboxPrivate> sandbox = mUnprivilegedJunkScope.get()) { + sandbox->ReleaseWrapper(sandbox); + mUnprivilegedJunkScope = nullptr; + } + mLoaderGlobal = nullptr; +} + +JSObject* XPCJSRuntime::LoaderGlobal() { + if (!mLoaderGlobal) { + RefPtr loader = mozJSModuleLoader::Get(); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + + mLoaderGlobal = loader->GetSharedGlobal(jsapi.cx()); + MOZ_RELEASE_ASSERT(!JS_IsExceptionPending(jsapi.cx())); + } + return mLoaderGlobal; +} + +uint32_t GetAndClampCPUCount() { + // See HelperThreads.cpp for why we want between 2-8 threads + int32_t proc = GetNumberOfProcessors(); + if (proc < 2) { + return 2; + } + return std::min(proc, 8); +} diff --git a/js/xpconnect/src/XPCJSWeakReference.cpp b/js/xpconnect/src/XPCJSWeakReference.cpp new file mode 100644 index 0000000000..fbf2e22441 --- /dev/null +++ b/js/xpconnect/src/XPCJSWeakReference.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "XPCJSWeakReference.h" + +#include "nsContentUtils.h" + +using namespace JS; + +xpcJSWeakReference::xpcJSWeakReference() = default; + +NS_IMPL_ISUPPORTS(xpcJSWeakReference, xpcIJSWeakReference) + +nsresult xpcJSWeakReference::Init(JSContext* cx, const JS::Value& object) { + if (!object.isObject()) { + return NS_OK; + } + + JS::RootedObject obj(cx, &object.toObject()); + + XPCCallContext ccx(cx); + + // See if the object is a wrapped native that supports weak references. + nsCOMPtr<nsISupports> supports = xpc::ReflectorToISupportsDynamic(obj, cx); + nsCOMPtr<nsISupportsWeakReference> supportsWeakRef = + do_QueryInterface(supports); + if (supportsWeakRef) { + supportsWeakRef->GetWeakReference(getter_AddRefs(mReferent)); + if (mReferent) { + return NS_OK; + } + } + // If it's not a wrapped native, or it is a wrapped native that does not + // support weak references, fall back to getting a weak ref to the object. + + // See if object is a wrapped JSObject. + RefPtr<nsXPCWrappedJS> wrapped; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(cx, obj, NS_GET_IID(nsISupports), + getter_AddRefs(wrapped)); + if (!wrapped) { + NS_ERROR("can't get nsISupportsWeakReference wrapper for obj"); + return rv; + } + + return wrapped->GetWeakReference(getter_AddRefs(mReferent)); +} + +NS_IMETHODIMP +xpcJSWeakReference::Get(JSContext* aCx, MutableHandleValue aRetval) { + aRetval.setNull(); + + if (!mReferent) { + return NS_OK; + } + + nsCOMPtr<nsISupports> supports = do_QueryReferent(mReferent); + if (!supports) { + return NS_OK; + } + + nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(supports); + if (!wrappedObj) { + // We have a generic XPCOM object that supports weak references here. + // Wrap it and pass it out. + return nsContentUtils::WrapNative(aCx, supports, &NS_GET_IID(nsISupports), + aRetval); + } + + JS::RootedObject obj(aCx, wrappedObj->GetJSObject()); + if (!obj) { + return NS_OK; + } + + // Most users of XPCWrappedJS don't need to worry about + // re-wrapping because things are implicitly rewrapped by + // xpcconvert. However, because we're doing this directly + // through the native call context, we need to call + // JS_WrapObject(). + if (!JS_WrapObject(aCx, &obj)) { + return NS_ERROR_FAILURE; + } + + aRetval.setObject(*obj); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCJSWeakReference.h b/js/xpconnect/src/XPCJSWeakReference.h new file mode 100644 index 0000000000..b70b5dd9fd --- /dev/null +++ b/js/xpconnect/src/XPCJSWeakReference.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +#ifndef xpcjsweakreference_h___ +#define xpcjsweakreference_h___ + +#include "xpcIJSWeakReference.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/Attributes.h" + +class xpcJSWeakReference final : public xpcIJSWeakReference { + ~xpcJSWeakReference() = default; + + public: + xpcJSWeakReference(); + nsresult Init(JSContext* cx, const JS::Value& object); + + NS_DECL_ISUPPORTS + NS_DECL_XPCIJSWEAKREFERENCE + + private: + nsWeakPtr mReferent; +}; + +#endif // xpcjsweakreference_h___ diff --git a/js/xpconnect/src/XPCLocale.cpp b/js/xpconnect/src/XPCLocale.cpp new file mode 100644 index 0000000000..c61a25e17e --- /dev/null +++ b/js/xpconnect/src/XPCLocale.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include "js/LocaleSensitive.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsComponentManagerUtils.h" +#include "nsIPrefService.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/Preferences.h" + +#include "xpcpublic.h" +#include "xpcprivate.h" + +using namespace mozilla; +using mozilla::intl::LocaleService; + +class XPCLocaleObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(); + + private: + virtual ~XPCLocaleObserver() = default; +}; + +NS_IMPL_ISUPPORTS(XPCLocaleObserver, nsIObserver); + +void XPCLocaleObserver::Init() { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + observerService->AddObserver(this, "intl:app-locales-changed", false); + + Preferences::AddStrongObserver(this, "javascript.use_us_english_locale"); +} + +NS_IMETHODIMP +XPCLocaleObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "intl:app-locales-changed") || + (!strcmp(aTopic, "nsPref:changed") && + !NS_strcmp(aData, u"javascript.use_us_english_locale"))) { + JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime(); + if (!xpc_LocalizeRuntime(rt)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +/** + * JS locale callbacks implemented by XPCOM modules. These are theoretically + * safe for use on multiple threads. Unfortunately, the intl code underlying + * these XPCOM modules doesn't yet support this, so in practice + * XPCLocaleCallbacks are limited to the main thread. + */ +struct XPCLocaleCallbacks : public JSLocaleCallbacks { + XPCLocaleCallbacks() { + MOZ_COUNT_CTOR(XPCLocaleCallbacks); + + // Disable the toLocaleUpper/Lower case hooks to use the standard, + // locale-insensitive definition from String.prototype. (These hooks are + // only consulted when JS_HAS_INTL_API is not set.) Since JS_HAS_INTL_API + // is always set, these hooks should be disabled. + localeToUpperCase = nullptr; + localeToLowerCase = nullptr; + localeCompare = nullptr; + localeToUnicode = nullptr; + + // It's going to be retained by the ObserverService. + RefPtr<XPCLocaleObserver> locObs = new XPCLocaleObserver(); + locObs->Init(); + } + + ~XPCLocaleCallbacks() { + AssertThreadSafety(); + MOZ_COUNT_DTOR(XPCLocaleCallbacks); + } + + /** + * Return the XPCLocaleCallbacks that's hidden away in |rt|. (This impl uses + * the locale callbacks struct to store away its per-context data.) + */ + static XPCLocaleCallbacks* This(JSRuntime* rt) { + // Locale information for |cx| was associated using xpc_LocalizeContext; + // assert and double-check this. + const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(rt); + MOZ_ASSERT(lc); + MOZ_ASSERT(lc->localeToUpperCase == nullptr); + MOZ_ASSERT(lc->localeToLowerCase == nullptr); + MOZ_ASSERT(lc->localeCompare == nullptr); + MOZ_ASSERT(lc->localeToUnicode == nullptr); + + const XPCLocaleCallbacks* ths = static_cast<const XPCLocaleCallbacks*>(lc); + ths->AssertThreadSafety(); + return const_cast<XPCLocaleCallbacks*>(ths); + } + + private: + void AssertThreadSafety() const { + NS_ASSERT_OWNINGTHREAD(XPCLocaleCallbacks); + } + + NS_DECL_OWNINGTHREAD +}; + +bool xpc_LocalizeRuntime(JSRuntime* rt) { + // We want to assign the locale callbacks only the first time we + // localize the context. + // All consequent calls to this function are result of language changes + // and should not assign it again. + const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(rt); + if (!lc) { + JS_SetLocaleCallbacks(rt, new XPCLocaleCallbacks()); + } + + // Set the default locale. + + // Check a pref to see if we should use US English locale regardless + // of the system locale. + if (Preferences::GetBool("javascript.use_us_english_locale", false)) { + return JS_SetDefaultLocale(rt, "en-US"); + } + + // No pref has been found, so get the default locale from the + // regional prefs locales. + AutoTArray<nsCString, 10> rpLocales; + LocaleService::GetInstance()->GetRegionalPrefsLocales(rpLocales); + + MOZ_ASSERT(rpLocales.Length() > 0); + return JS_SetDefaultLocale(rt, rpLocales[0].get()); +} + +void xpc_DelocalizeRuntime(JSRuntime* rt) { + const XPCLocaleCallbacks* lc = XPCLocaleCallbacks::This(rt); + JS_SetLocaleCallbacks(rt, nullptr); + delete lc; +} diff --git a/js/xpconnect/src/XPCLog.cpp b/js/xpconnect/src/XPCLog.cpp new file mode 100644 index 0000000000..e0efa87fa1 --- /dev/null +++ b/js/xpconnect/src/XPCLog.cpp @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +/* Debug Logging support. */ + +#include "XPCLog.h" +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "mozilla/mozalloc.h" +#include "prlog.h" +#include <string.h> +#include <stdarg.h> + +// this all only works for DEBUG... +#ifdef DEBUG + +# define SPACE_COUNT 200 +# define LINE_LEN 200 +# define INDENT_FACTOR 2 + +# define CAN_RUN (g_InitState == 1 || (g_InitState == 0 && Init())) + +static char* g_Spaces; +static int g_InitState = 0; +static int g_Indent = 0; +static mozilla::LazyLogModule g_LogMod("xpclog"); + +static bool Init() { + g_Spaces = new char[SPACE_COUNT + 1]; + if (!g_Spaces || !MOZ_LOG_TEST(g_LogMod, mozilla::LogLevel::Error)) { + g_InitState = 1; + XPC_Log_Finish(); + return false; + } + memset(g_Spaces, ' ', SPACE_COUNT); + g_Spaces[SPACE_COUNT] = 0; + g_InitState = 1; + return true; +} + +void XPC_Log_Finish() { + if (g_InitState == 1) { + delete[] g_Spaces; + } + g_InitState = -1; +} + +void XPC_Log_print(const char* fmt, ...) { + va_list ap; + char line[LINE_LEN]; + + va_start(ap, fmt); + VsprintfLiteral(line, fmt, ap); + va_end(ap); + if (g_Indent) { + PR_LogPrint("%s%s", g_Spaces + SPACE_COUNT - (INDENT_FACTOR * g_Indent), + line); + } else { + PR_LogPrint("%s", line); + } +} + +bool XPC_Log_Check(int i) { + return CAN_RUN && MOZ_LOG_TEST(g_LogMod, mozilla::LogLevel::Error); +} + +void XPC_Log_Indent() { + if (INDENT_FACTOR * (++g_Indent) > SPACE_COUNT) { + g_Indent--; + } +} + +void XPC_Log_Outdent() { + if (--g_Indent < 0) { + g_Indent++; + } +} + +void XPC_Log_Clear_Indent() { g_Indent = 0; } + +#endif diff --git a/js/xpconnect/src/XPCLog.h b/js/xpconnect/src/XPCLog.h new file mode 100644 index 0000000000..66bacf90e2 --- /dev/null +++ b/js/xpconnect/src/XPCLog.h @@ -0,0 +1,69 @@ +/* -*- 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/. */ + +/* Debug Logging support. */ + +#ifndef xpclog_h___ +#define xpclog_h___ + +#include "mozilla/Attributes.h" +#include "mozilla/Logging.h" + +/* + * This uses mozilla/Logging.h. The module name used here is 'xpclog'. + * These environment settings should work... + * + * SET MOZ_LOG=xpclog:5 + * SET MOZ_LOG_FILE=logfile.txt + * + * usage: + * XPC_LOG_ERROR(("my comment number %d", 5)) // note the double parens + * + */ + +#ifdef DEBUG +# define XPC_LOG_INTERNAL(number, _args) \ + do { \ + if (XPC_Log_Check(number)) { \ + XPC_Log_print _args; \ + } \ + } while (0) + +# define XPC_LOG_ALWAYS(_args) XPC_LOG_INTERNAL(1, _args) +# define XPC_LOG_ERROR(_args) XPC_LOG_INTERNAL(2, _args) +# define XPC_LOG_WARNING(_args) XPC_LOG_INTERNAL(3, _args) +# define XPC_LOG_DEBUG(_args) XPC_LOG_INTERNAL(4, _args) +# define XPC_LOG_FLUSH() PR_LogFlush() +# define XPC_LOG_INDENT() XPC_Log_Indent() +# define XPC_LOG_OUTDENT() XPC_Log_Outdent() +# define XPC_LOG_CLEAR_INDENT() XPC_Log_Clear_Indent() +# define XPC_LOG_FINISH() XPC_Log_Finish() + +extern "C" { + +void XPC_Log_print(const char* fmt, ...) MOZ_FORMAT_PRINTF(1, 2); +bool XPC_Log_Check(int i); +void XPC_Log_Indent(); +void XPC_Log_Outdent(); +void XPC_Log_Clear_Indent(); +void XPC_Log_Finish(); + +} // extern "C" + +#else + +# define XPC_LOG_ALWAYS(_args) ((void)0) +# define XPC_LOG_ERROR(_args) ((void)0) +# define XPC_LOG_WARNING(_args) ((void)0) +# define XPC_LOG_DEBUG(_args) ((void)0) +# define XPC_LOG_FLUSH() ((void)0) +# define XPC_LOG_INDENT() ((void)0) +# define XPC_LOG_OUTDENT() ((void)0) +# define XPC_LOG_CLEAR_INDENT() ((void)0) +# define XPC_LOG_FINISH() ((void)0) +#endif + +#endif /* xpclog_h___ */ diff --git a/js/xpconnect/src/XPCMaps.cpp b/js/xpconnect/src/XPCMaps.cpp new file mode 100644 index 0000000000..e2d2857a82 --- /dev/null +++ b/js/xpconnect/src/XPCMaps.cpp @@ -0,0 +1,191 @@ +/* -*- 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/. */ + +/* Private maps (hashtables). */ + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "xpcprivate.h" +#include "XPCMaps.h" + +#include "js/HashTable.h" + +using namespace mozilla; + +/***************************************************************************/ +// implement JSObject2WrappedJSMap... + +void JSObject2WrappedJSMap::UpdateWeakPointersAfterGC(JSTracer* trc) { + // Check all wrappers and update their JSObject pointer if it has been + // moved. Release any wrappers whose weakly held JSObject has died. + + nsTArray<RefPtr<nsXPCWrappedJS>> dying; + for (auto iter = mTable.modIter(); !iter.done(); iter.next()) { + nsXPCWrappedJS* wrapper = iter.get().value(); + MOZ_ASSERT(wrapper, "found a null JS wrapper!"); + + // There's no need to walk the entire chain, because only the root can be + // subject to finalization due to the double release behavior in Release. + // See the comment at the top of XPCWrappedJS.cpp about nsXPCWrappedJS + // lifetime. + if (wrapper && wrapper->IsSubjectToFinalization()) { + wrapper->UpdateObjectPointerAfterGC(trc); + if (!wrapper->GetJSObjectPreserveColor()) { + dying.AppendElement(dont_AddRef(wrapper)); + } + } + + // Remove or update the JSObject key in the table if necessary. + if (!JS_UpdateWeakPointerAfterGC(trc, &iter.get().mutableKey())) { + iter.remove(); + } + } +} + +void JSObject2WrappedJSMap::ShutdownMarker() { + for (auto iter = mTable.iter(); !iter.done(); iter.next()) { + nsXPCWrappedJS* wrapper = iter.get().value(); + MOZ_ASSERT(wrapper, "found a null JS wrapper!"); + MOZ_ASSERT(wrapper->IsValid(), "found an invalid JS wrapper!"); + wrapper->SystemIsBeingShutDown(); + } +} + +size_t JSObject2WrappedJSMap::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + n += mTable.shallowSizeOfExcludingThis(mallocSizeOf); + return n; +} + +size_t JSObject2WrappedJSMap::SizeOfWrappedJS( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = 0; + for (auto iter = mTable.iter(); !iter.done(); iter.next()) { + n += iter.get().value()->SizeOfIncludingThis(mallocSizeOf); + } + return n; +} + +/***************************************************************************/ +// implement Native2WrappedNativeMap... + +Native2WrappedNativeMap::Native2WrappedNativeMap() + : mMap(XPC_NATIVE_MAP_LENGTH) {} + +size_t Native2WrappedNativeMap::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + n += mMap.shallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mMap.iter(); !iter.done(); iter.next()) { + n += mallocSizeOf(iter.get().value()); + } + return n; +} + +/***************************************************************************/ +// implement IID2NativeInterfaceMap... + +IID2NativeInterfaceMap::IID2NativeInterfaceMap() + : mMap(XPC_NATIVE_INTERFACE_MAP_LENGTH) {} + +size_t IID2NativeInterfaceMap::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + n += mMap.shallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mMap.iter(); !iter.done(); iter.next()) { + n += iter.get().value()->SizeOfIncludingThis(mallocSizeOf); + } + return n; +} + +/***************************************************************************/ +// implement ClassInfo2NativeSetMap... + +ClassInfo2NativeSetMap::ClassInfo2NativeSetMap() + : mMap(XPC_NATIVE_SET_MAP_LENGTH) {} + +size_t ClassInfo2NativeSetMap::ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) { + size_t n = mallocSizeOf(this); + n += mMap.shallowSizeOfExcludingThis(mallocSizeOf); + return n; +} + +/***************************************************************************/ +// implement ClassInfo2WrappedNativeProtoMap... + +ClassInfo2WrappedNativeProtoMap::ClassInfo2WrappedNativeProtoMap() + : mMap(XPC_NATIVE_PROTO_MAP_LENGTH) {} + +size_t ClassInfo2WrappedNativeProtoMap::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + n += mMap.shallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mMap.iter(); !iter.done(); iter.next()) { + n += mallocSizeOf(iter.get().value()); + } + return n; +} + +/***************************************************************************/ +// implement NativeSetMap... + +bool NativeSetHasher::match(Key key, Lookup lookup) { + // The |key| argument is for the existing table entry and |lookup| is the + // value passed by the caller that is being compared with it. + XPCNativeSet* SetInTable = key; + XPCNativeSet* Set = lookup->GetBaseSet(); + XPCNativeInterface* Addition = lookup->GetAddition(); + + if (!Set) { + // This is a special case to deal with the invariant that says: + // "All sets have exactly one nsISupports interface and it comes first." + // See XPCNativeSet::NewInstance for details. + // + // Though we might have a key that represents only one interface, we + // know that if that one interface were contructed into a set then + // it would end up really being a set with two interfaces (except for + // the case where the one interface happened to be nsISupports). + + return (SetInTable->GetInterfaceCount() == 1 && + SetInTable->GetInterfaceAt(0) == Addition) || + (SetInTable->GetInterfaceCount() == 2 && + SetInTable->GetInterfaceAt(1) == Addition); + } + + if (!Addition && Set == SetInTable) { + return true; + } + + uint16_t count = Set->GetInterfaceCount(); + if (count + (Addition ? 1 : 0) != SetInTable->GetInterfaceCount()) { + return false; + } + + XPCNativeInterface** CurrentInTable = SetInTable->GetInterfaceArray(); + XPCNativeInterface** Current = Set->GetInterfaceArray(); + for (uint16_t i = 0; i < count; i++) { + if (*(Current++) != *(CurrentInTable++)) { + return false; + } + } + return !Addition || Addition == *(CurrentInTable++); +} + +NativeSetMap::NativeSetMap() : mSet(XPC_NATIVE_SET_MAP_LENGTH) {} + +size_t NativeSetMap::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t n = mallocSizeOf(this); + n += mSet.shallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mSet.iter(); !iter.done(); iter.next()) { + n += iter.get()->SizeOfIncludingThis(mallocSizeOf); + } + return n; +} + +/***************************************************************************/ diff --git a/js/xpconnect/src/XPCMaps.h b/js/xpconnect/src/XPCMaps.h new file mode 100644 index 0000000000..f1d32ab61c --- /dev/null +++ b/js/xpconnect/src/XPCMaps.h @@ -0,0 +1,386 @@ +/* -*- 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/. */ + +/* Private maps (hashtables). */ + +#ifndef xpcmaps_h___ +#define xpcmaps_h___ + +#include "mozilla/AllocPolicy.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/HashTable.h" + +#include "js/GCHashTable.h" + +/***************************************************************************/ +// default initial sizes for maps (hashtables) + +#define XPC_JS_MAP_LENGTH 32 + +#define XPC_NATIVE_MAP_LENGTH 8 +#define XPC_NATIVE_PROTO_MAP_LENGTH 8 +#define XPC_DYING_NATIVE_PROTO_MAP_LENGTH 8 +#define XPC_NATIVE_INTERFACE_MAP_LENGTH 32 +#define XPC_NATIVE_SET_MAP_LENGTH 32 +#define XPC_WRAPPER_MAP_LENGTH 8 + +/*************************/ + +class JSObject2WrappedJSMap { + using Map = js::HashMap<JS::Heap<JSObject*>, nsXPCWrappedJS*, + js::StableCellHasher<JS::Heap<JSObject*>>, + InfallibleAllocPolicy>; + + public: + JSObject2WrappedJSMap() = default; + + inline nsXPCWrappedJS* Find(JSObject* Obj) { + MOZ_ASSERT(Obj, "bad param"); + Map::Ptr p = mTable.lookup(Obj); + return p ? p->value() : nullptr; + } + +#ifdef DEBUG + inline bool HasWrapper(nsXPCWrappedJS* wrapper) { + for (auto iter = mTable.iter(); !iter.done(); iter.next()) { + if (iter.get().value() == wrapper) { + return true; + } + } + return false; + } +#endif + + inline nsXPCWrappedJS* Add(JSContext* cx, nsXPCWrappedJS* wrapper) { + MOZ_ASSERT(wrapper, "bad param"); + JSObject* obj = wrapper->GetJSObjectPreserveColor(); + Map::AddPtr p = mTable.lookupForAdd(obj); + if (p) { + return p->value(); + } + if (!mTable.add(p, obj, wrapper)) { + return nullptr; + } + return wrapper; + } + + inline void Remove(nsXPCWrappedJS* wrapper) { + MOZ_ASSERT(wrapper, "bad param"); + mTable.remove(wrapper->GetJSObjectPreserveColor()); + } + + inline uint32_t Count() { return mTable.count(); } + + inline void Dump(int16_t depth) { + for (auto iter = mTable.iter(); !iter.done(); iter.next()) { + iter.get().value()->DebugDump(depth); + } + } + + void UpdateWeakPointersAfterGC(JSTracer* trc); + + void ShutdownMarker(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + // Report the sum of SizeOfIncludingThis() for all wrapped JS in the map. + // Each wrapped JS is only in one map. + size_t SizeOfWrappedJS(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + Map mTable{XPC_JS_MAP_LENGTH}; +}; + +/*************************/ + +class Native2WrappedNativeMap { + using Map = mozilla::HashMap<nsISupports*, XPCWrappedNative*, + mozilla::DefaultHasher<nsISupports*>, + mozilla::MallocAllocPolicy>; + + public: + Native2WrappedNativeMap(); + + XPCWrappedNative* Find(nsISupports* obj) const { + MOZ_ASSERT(obj, "bad param"); + Map::Ptr ptr = mMap.lookup(obj); + return ptr ? ptr->value() : nullptr; + } + + XPCWrappedNative* Add(XPCWrappedNative* wrapper) { + MOZ_ASSERT(wrapper, "bad param"); + nsISupports* obj = wrapper->GetIdentityObject(); + Map::AddPtr ptr = mMap.lookupForAdd(obj); + MOZ_ASSERT(!ptr, "wrapper already in new scope!"); + if (ptr) { + return ptr->value(); + } + if (!mMap.add(ptr, obj, wrapper)) { + return nullptr; + } + return wrapper; + } + + void Clear() { mMap.clear(); } + + uint32_t Count() { return mMap.count(); } + + Map::Iterator Iter() { return mMap.iter(); } + Map::ModIterator ModIter() { return mMap.modIter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + Map mMap; +}; + +/*************************/ + +struct IIDHasher { + using Key = const nsIID*; + using Lookup = Key; + + // Note this is returning the hash of the bit pattern of the first part of the + // nsID, not the hash of the pointer to the nsID. + static mozilla::HashNumber hash(Lookup lookup) { + uintptr_t v; + memcpy(&v, lookup, sizeof(v)); + return mozilla::HashGeneric(v); + } + + static bool match(Key key, Lookup lookup) { return key->Equals(*lookup); } +}; + +class IID2NativeInterfaceMap { + using Map = mozilla::HashMap<const nsIID*, XPCNativeInterface*, IIDHasher, + mozilla::MallocAllocPolicy>; + + public: + IID2NativeInterfaceMap(); + + XPCNativeInterface* Find(REFNSIID iid) const { + Map::Ptr ptr = mMap.lookup(&iid); + return ptr ? ptr->value() : nullptr; + } + + bool AddNew(XPCNativeInterface* iface) { + MOZ_ASSERT(iface, "bad param"); + const nsIID* iid = iface->GetIID(); + return mMap.putNew(iid, iface); + } + + void Remove(XPCNativeInterface* iface) { + MOZ_ASSERT(iface, "bad param"); + mMap.remove(iface->GetIID()); + } + + uint32_t Count() { return mMap.count(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + void Trace(JSTracer* trc); + + private: + Map mMap; +}; + +/*************************/ + +class ClassInfo2NativeSetMap { + using Map = mozilla::HashMap<nsIClassInfo*, RefPtr<XPCNativeSet>, + mozilla::DefaultHasher<nsIClassInfo*>, + mozilla::MallocAllocPolicy>; + + public: + ClassInfo2NativeSetMap(); + + XPCNativeSet* Find(nsIClassInfo* info) const { + auto ptr = mMap.lookup(info); + return ptr ? ptr->value().get() : nullptr; + } + + XPCNativeSet* Add(nsIClassInfo* info, XPCNativeSet* set) { + MOZ_ASSERT(info, "bad param"); + auto ptr = mMap.lookupForAdd(info); + if (ptr) { + return ptr->value(); + } + if (!mMap.add(ptr, info, set)) { + return nullptr; + } + return set; + } + + void Remove(nsIClassInfo* info) { + MOZ_ASSERT(info, "bad param"); + mMap.remove(info); + } + + uint32_t Count() { return mMap.count(); } + + // ClassInfo2NativeSetMap holds pointers to *some* XPCNativeSets. + // So we don't want to count those XPCNativeSets, because they are better + // counted elsewhere (i.e. in XPCJSContext::mNativeSetMap, which holds + // pointers to *all* XPCNativeSets). Hence the "Shallow". + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + private: + Map mMap; +}; + +/*************************/ + +class ClassInfo2WrappedNativeProtoMap { + using Map = mozilla::HashMap<nsIClassInfo*, XPCWrappedNativeProto*, + mozilla::DefaultHasher<nsIClassInfo*>, + mozilla::MallocAllocPolicy>; + + public: + ClassInfo2WrappedNativeProtoMap(); + + XPCWrappedNativeProto* Find(nsIClassInfo* info) const { + auto ptr = mMap.lookup(info); + return ptr ? ptr->value() : nullptr; + } + + XPCWrappedNativeProto* Add(nsIClassInfo* info, XPCWrappedNativeProto* proto) { + MOZ_ASSERT(info, "bad param"); + auto ptr = mMap.lookupForAdd(info); + if (ptr) { + return ptr->value(); + } + if (!mMap.add(ptr, info, proto)) { + return nullptr; + } + return proto; + } + + void Clear() { mMap.clear(); } + + uint32_t Count() { return mMap.count(); } + + Map::Iterator Iter() { return mMap.iter(); } + Map::ModIterator ModIter() { return mMap.modIter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + Map mMap; +}; + +/*************************/ + +struct NativeSetHasher { + using Key = XPCNativeSet*; + using Lookup = const XPCNativeSetKey*; + + static mozilla::HashNumber hash(Lookup lookup) { return lookup->Hash(); } + static bool match(Key key, Lookup lookup); +}; + +class NativeSetMap { + using Set = mozilla::HashSet<XPCNativeSet*, NativeSetHasher, + mozilla::MallocAllocPolicy>; + + public: + NativeSetMap(); + + XPCNativeSet* Find(const XPCNativeSetKey* key) const { + auto ptr = mSet.lookup(key); + return ptr ? *ptr : nullptr; + } + + XPCNativeSet* Add(const XPCNativeSetKey* key, XPCNativeSet* set) { + MOZ_ASSERT(key, "bad param"); + MOZ_ASSERT(set, "bad param"); + auto ptr = mSet.lookupForAdd(key); + if (ptr) { + return *ptr; + } + if (!mSet.add(ptr, set)) { + return nullptr; + } + return set; + } + + bool AddNew(const XPCNativeSetKey* key, XPCNativeSet* set) { + XPCNativeSet* set2 = Add(key, set); + if (!set2) { + return false; + } +#ifdef DEBUG + XPCNativeSetKey key2(set); + MOZ_ASSERT(key->Hash() == key2.Hash()); + MOZ_ASSERT(set2 == set, "Should not have found an existing entry"); +#endif + return true; + } + + void Remove(XPCNativeSet* set) { + MOZ_ASSERT(set, "bad param"); + + XPCNativeSetKey key(set); + mSet.remove(&key); + } + + uint32_t Count() { return mSet.count(); } + + Set::Iterator Iter() { return mSet.iter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + private: + Set mSet; +}; + +/***************************************************************************/ + +class JSObject2JSObjectMap { + using Map = JS::GCHashMap<JS::Heap<JSObject*>, JS::Heap<JSObject*>, + js::StableCellHasher<JS::Heap<JSObject*>>, + js::SystemAllocPolicy>; + + public: + JSObject2JSObjectMap() = default; + + inline JSObject* Find(JSObject* key) { + MOZ_ASSERT(key, "bad param"); + if (Map::Ptr p = mTable.lookup(key)) { + return p->value(); + } + return nullptr; + } + + /* Note: If the entry already exists, return the old value. */ + inline JSObject* Add(JSContext* cx, JSObject* key, JSObject* value) { + MOZ_ASSERT(key, "bad param"); + Map::AddPtr p = mTable.lookupForAdd(key); + if (p) { + JSObject* oldValue = p->value(); + p->value() = value; + return oldValue; + } + if (!mTable.add(p, key, value)) { + return nullptr; + } + MOZ_ASSERT(xpc::ObjectScope(key)->mWaiverWrapperMap == this); + return value; + } + + inline void Remove(JSObject* key) { + MOZ_ASSERT(key, "bad param"); + mTable.remove(key); + } + + inline uint32_t Count() { return mTable.count(); } + + void UpdateWeakPointers(JSTracer* trc) { mTable.traceWeak(trc); } + + private: + Map mTable{XPC_WRAPPER_MAP_LENGTH}; +}; + +#endif /* xpcmaps_h___ */ diff --git a/js/xpconnect/src/XPCModule.cpp b/js/xpconnect/src/XPCModule.cpp new file mode 100644 index 0000000000..c668df2bb3 --- /dev/null +++ b/js/xpconnect/src/XPCModule.cpp @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#define XPCONNECT_MODULE +#include "xpcprivate.h" + +nsresult xpcModuleCtor() { + nsXPConnect::InitStatics(); + + return NS_OK; +} + +void xpcModuleDtor() { + // Release our singletons + nsXPConnect::ReleaseXPConnectSingleton(); +} diff --git a/js/xpconnect/src/XPCModule.h b/js/xpconnect/src/XPCModule.h new file mode 100644 index 0000000000..d539750753 --- /dev/null +++ b/js/xpconnect/src/XPCModule.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "mozJSSubScriptLoader.h" + +/* Module implementation for the xpconnect library. */ + +#define XPCVARIANT_CONTRACTID "@mozilla.org/xpcvariant;1" + +// {FE4F7592-C1FC-4662-AC83-538841318803} +#define SCRIPTABLE_INTERFACES_CID \ + { \ + 0xfe4f7592, 0xc1fc, 0x4662, { \ + 0xac, 0x83, 0x53, 0x88, 0x41, 0x31, 0x88, 0x3 \ + } \ + } + +#define MOZJSSUBSCRIPTLOADER_CONTRACTID "@mozilla.org/moz/jssubscript-loader;1" + +nsresult xpcModuleCtor(); +void xpcModuleDtor(); diff --git a/js/xpconnect/src/XPCRuntimeService.cpp b/js/xpconnect/src/XPCRuntimeService.cpp new file mode 100644 index 0000000000..2c283ea2bc --- /dev/null +++ b/js/xpconnect/src/XPCRuntimeService.cpp @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" + +#include "nsContentUtils.h" +#include "BackstagePass.h" +#include "mozilla/Result.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/WebIDLGlobalNameHash.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" + +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(BackstagePass, nsIXPCScriptable, nsIGlobalObject, + nsIClassInfo, nsIScriptObjectPrincipal, + nsISupportsWeakReference) + +BackstagePass::BackstagePass() + : mPrincipal(nsContentUtils::GetSystemPrincipal()), mWrapper(nullptr) {} + +// XXX(nika): It appears we don't have support for mayresolve hooks in +// nsIXPCScriptable, and I don't really want to add it because I'd rather just +// kill nsIXPCScriptable alltogether, so we don't use it here. + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME BackstagePass +#define XPC_MAP_QUOTED_CLASSNAME "BackstagePass" +#define XPC_MAP_FLAGS \ + (XPC_SCRIPTABLE_WANT_RESOLVE | XPC_SCRIPTABLE_WANT_NEWENUMERATE | \ + XPC_SCRIPTABLE_WANT_FINALIZE | XPC_SCRIPTABLE_WANT_PRECREATE | \ + XPC_SCRIPTABLE_USE_JSSTUB_FOR_ADDPROPERTY | \ + XPC_SCRIPTABLE_USE_JSSTUB_FOR_DELPROPERTY | \ + XPC_SCRIPTABLE_DONT_ENUM_QUERY_INTERFACE | \ + XPC_SCRIPTABLE_IS_GLOBAL_OBJECT | \ + XPC_SCRIPTABLE_DONT_REFLECT_INTERFACE_NAMES) +#include "xpc_map_end.h" /* This will #undef the above */ + +JSObject* BackstagePass::GetGlobalJSObject() { + if (mWrapper) { + return mWrapper->GetFlatJSObject(); + } + return nullptr; +} + +JSObject* BackstagePass::GetGlobalJSObjectPreserveColor() const { + if (mWrapper) { + return mWrapper->GetFlatJSObjectPreserveColor(); + } + return nullptr; +} + +void BackstagePass::SetGlobalObject(JSObject* global) { + nsISupports* p = XPCWrappedNative::Get(global); + MOZ_ASSERT(p); + mWrapper = static_cast<XPCWrappedNative*>(p); +} + +NS_IMETHODIMP +BackstagePass::Resolve(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, jsid idArg, bool* resolvedp, + bool* _retval) { + JS::RootedObject obj(cx, objArg); + JS::RootedId id(cx, idArg); + *_retval = + WebIDLGlobalNameHash::ResolveForSystemGlobal(cx, obj, id, resolvedp); + if (!*_retval) { + return NS_ERROR_FAILURE; + } + + if (*resolvedp) { + return NS_OK; + } + + XPCJSContext* xpccx = XPCJSContext::Get(); + if (id == xpccx->GetStringID(XPCJSContext::IDX_FETCH)) { + *_retval = xpc::SandboxCreateFetch(cx, obj); + if (!*_retval) { + return NS_ERROR_FAILURE; + } + *resolvedp = true; + } else if (id == xpccx->GetStringID(XPCJSContext::IDX_CRYPTO)) { + *_retval = xpc::SandboxCreateCrypto(cx, obj); + if (!*_retval) { + return NS_ERROR_FAILURE; + } + *resolvedp = true; + } else if (id == xpccx->GetStringID(XPCJSContext::IDX_INDEXEDDB)) { + *_retval = IndexedDatabaseManager::DefineIndexedDB(cx, obj); + if (!*_retval) { + return NS_ERROR_FAILURE; + } + *resolvedp = true; + } else if (id == xpccx->GetStringID(XPCJSContext::IDX_STRUCTUREDCLONE)) { + *_retval = xpc::SandboxCreateStructuredClone(cx, obj); + if (!*_retval) { + return NS_ERROR_FAILURE; + } + *resolvedp = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::NewEnumerate(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, + JS::MutableHandleIdVector properties, + bool enumerableOnly, bool* _retval) { + JS::RootedObject obj(cx, objArg); + + XPCJSContext* xpccx = XPCJSContext::Get(); + if (!properties.append(xpccx->GetStringID(XPCJSContext::IDX_FETCH)) || + !properties.append(xpccx->GetStringID(XPCJSContext::IDX_CRYPTO)) || + !properties.append(xpccx->GetStringID(XPCJSContext::IDX_INDEXEDDB)) || + !properties.append( + xpccx->GetStringID(XPCJSContext::IDX_STRUCTUREDCLONE))) { + return NS_ERROR_FAILURE; + } + + *_retval = WebIDLGlobalNameHash::NewEnumerateSystemGlobal(cx, obj, properties, + enumerableOnly); + return *_retval ? NS_OK : NS_ERROR_FAILURE; +} + +/***************************************************************************/ +NS_IMETHODIMP +BackstagePass::GetInterfaces(nsTArray<nsIID>& aArray) { + aArray = nsTArray<nsIID>{NS_GET_IID(nsIXPCScriptable), + NS_GET_IID(nsIScriptObjectPrincipal)}; + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetScriptableHelper(nsIXPCScriptable** retval) { + nsCOMPtr<nsIXPCScriptable> scriptable = this; + scriptable.forget(retval); + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetContractID(nsACString& aContractID) { + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +BackstagePass::GetClassDescription(nsACString& aClassDescription) { + aClassDescription.AssignLiteral("BackstagePass"); + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetClassID(nsCID** aClassID) { + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetFlags(uint32_t* aFlags) { + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +BackstagePass::Finalize(nsIXPConnectWrappedNative* wrapper, JS::GCContext* gcx, + JSObject* obj) { + nsCOMPtr<nsIGlobalObject> bsp(do_QueryInterface(wrapper->Native())); + MOZ_ASSERT(bsp); + static_cast<BackstagePass*>(bsp.get())->ForgetGlobalObject(); + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::PreCreate(nsISupports* nativeObj, JSContext* cx, + JSObject* globalObj, JSObject** parentObj) { + // We do the same trick here as for WindowSH. Return the js global + // as parent, so XPConenct can find the right scope and the wrapper + // that already exists. + nsCOMPtr<nsIGlobalObject> global(do_QueryInterface(nativeObj)); + MOZ_ASSERT(global, "nativeObj not a global object!"); + + JSObject* jsglobal = global->GetGlobalJSObject(); + if (jsglobal) { + *parentObj = jsglobal; + } + return NS_OK; +} + +mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> +BackstagePass::GetStorageKey() { + MOZ_ASSERT(NS_IsMainThread()); + + mozilla::ipc::PrincipalInfo principalInfo; + nsresult rv = PrincipalToPrincipalInfo(mPrincipal, &principalInfo); + if (NS_FAILED(rv)) { + return mozilla::Err(rv); + } + + MOZ_ASSERT(principalInfo.type() == + mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo); + + return std::move(principalInfo); +} diff --git a/js/xpconnect/src/XPCSelfHostedShmem.cpp b/js/xpconnect/src/XPCSelfHostedShmem.cpp new file mode 100644 index 0000000000..e5c8578ccd --- /dev/null +++ b/js/xpconnect/src/XPCSelfHostedShmem.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "XPCSelfHostedShmem.h" +#include "xpcprivate.h" + +// static +mozilla::StaticRefPtr<xpc::SelfHostedShmem> + xpc::SelfHostedShmem::sSelfHostedXdr; + +NS_IMPL_ISUPPORTS(xpc::SelfHostedShmem, nsIMemoryReporter) + +// static +xpc::SelfHostedShmem& xpc::SelfHostedShmem::GetSingleton() { + MOZ_ASSERT_IF(!sSelfHostedXdr, NS_IsMainThread()); + + if (!sSelfHostedXdr) { + sSelfHostedXdr = new SelfHostedShmem; + } + + return *sSelfHostedXdr; +} + +void xpc::SelfHostedShmem::InitMemoryReporter() { + mozilla::RegisterWeakMemoryReporter(this); +} + +// static +void xpc::SelfHostedShmem::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + // NOTE: We cannot call UnregisterWeakMemoryReporter(sSelfHostedXdr) as the + // service is shutdown at the time this call is made. In any cases, this would + // already be done when the memory reporter got destroyed. + sSelfHostedXdr = nullptr; +} + +void xpc::SelfHostedShmem::InitFromParent(ContentType aXdr) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mLen, "Shouldn't call this more than once"); + + size_t len = aXdr.Length(); + auto shm = mozilla::MakeUnique<base::SharedMemory>(); + if (NS_WARN_IF(!shm->CreateFreezeable(len))) { + return; + } + + if (NS_WARN_IF(!shm->Map(len))) { + return; + } + + void* address = shm->memory(); + memcpy(address, aXdr.Elements(), aXdr.LengthBytes()); + + base::SharedMemory roCopy; + if (NS_WARN_IF(!shm->ReadOnlyCopy(&roCopy))) { + return; + } + + mMem = std::move(shm); + mHandle = roCopy.TakeHandle(); + mLen = len; +} + +bool xpc::SelfHostedShmem::InitFromChild(::base::SharedMemoryHandle aHandle, + size_t aLen) { + MOZ_ASSERT(!XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mLen, "Shouldn't call this more than once"); + + auto shm = mozilla::MakeUnique<base::SharedMemory>(); + if (NS_WARN_IF(!shm->SetHandle(std::move(aHandle), /* read_only */ true))) { + return false; + } + + if (NS_WARN_IF(!shm->Map(aLen))) { + return false; + } + + // Note: mHandle remains empty, as content processes are not spawning more + // content processes. + mMem = std::move(shm); + mLen = aLen; + return true; +} + +xpc::SelfHostedShmem::ContentType xpc::SelfHostedShmem::Content() const { + if (!mMem) { + MOZ_ASSERT(mLen == 0); + return ContentType(); + } + return ContentType(reinterpret_cast<uint8_t*>(mMem->memory()), mLen); +} + +const mozilla::UniqueFileHandle& xpc::SelfHostedShmem::Handle() const { + return mHandle; +} + +NS_IMETHODIMP +xpc::SelfHostedShmem::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + // If this is the parent process, then we have a handle and this instance owns + // the data and shares it with other processes, otherwise this is shared data. + if (XRE_IsParentProcess()) { + // This does not exactly report the amount of data mapped by the system, + // but the space requested when creating the handle. + MOZ_COLLECT_REPORT("explicit/js-non-window/shared-memory/self-hosted-xdr", + KIND_NONHEAP, UNITS_BYTES, mLen, + "Memory used to initialize the JS engine with the " + "self-hosted code encoded by the parent process."); + } + return NS_OK; +} diff --git a/js/xpconnect/src/XPCSelfHostedShmem.h b/js/xpconnect/src/XPCSelfHostedShmem.h new file mode 100644 index 0000000000..0fcb1ed05c --- /dev/null +++ b/js/xpconnect/src/XPCSelfHostedShmem.h @@ -0,0 +1,89 @@ +/* -*- 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/. */ + +#ifndef xpcselfhostedshmem_h___ +#define xpcselfhostedshmem_h___ + +#include "base/shared_memory.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Span.h" +#include "mozilla/StaticPtr.h" +#include "nsIMemoryReporter.h" +#include "nsIObserver.h" +#include "nsIThread.h" + +namespace xpc { + +// This class is a singleton which holds a file-mapping of the Self Hosted +// Stencil XDR, to be shared by the parent process with all the child processes. +// +// It is either initialized by the parent process by monitoring when the Self +// hosted stencil is produced, or by the content process by reading a shared +// memory-mapped file. +class SelfHostedShmem final : public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + // NOTE: This type is identical to JS::SelfHostedCache, but we repeat it to + // avoid including JS engine API in ipc code. + using ContentType = mozilla::Span<const uint8_t>; + + static SelfHostedShmem& GetSingleton(); + + // Initialize this singleton with the content of the Self-hosted Stencil XDR. + // This will be used to initialize the shared memory which would hold a copy. + // + // This function is not thread-safe and should be call at most once and from + // the main thread. + void InitFromParent(ContentType aXdr); + + // Initialize this singleton with the content coming from the parent process, + // using a file handle which maps to the memory pages of the parent process. + // + // This function is not thread-safe and should be call at most once and from + // the main thread. + [[nodiscard]] bool InitFromChild(base::SharedMemoryHandle aHandle, + size_t aLen); + + // Return a span over the read-only XDR content of the self-hosted stencil. + ContentType Content() const; + + // Return the file handle which is under which the content is mapped. + const mozilla::UniqueFileHandle& Handle() const; + + // Register this class to the memory reporter service. + void InitMemoryReporter(); + + // Unregister this class from the memory reporter service, and delete the + // memory associated with the shared memory. As the memory is borrowed by the + // JS engine, this function should be called after JS_Shutdown. + // + // NOTE: This is not using the Observer service which would call Shutdown + // while JS code might still be running during shutdown process. + static void Shutdown(); + + private: + SelfHostedShmem() = default; + ~SelfHostedShmem() = default; + + static mozilla::StaticRefPtr<SelfHostedShmem> sSelfHostedXdr; + + // read-only file Handle used to transfer from the parent process to content + // processes. + mozilla::UniqueFileHandle mHandle; + + // Shared memory used by JS runtime initialization. + mozilla::UniquePtr<base::SharedMemory> mMem; + + // Length of the content within the shared memory. + size_t mLen = 0; +}; + +} // namespace xpc + +#endif // !xpcselfhostedshmem_h___ diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp new file mode 100644 index 0000000000..124c2ed37d --- /dev/null +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -0,0 +1,1539 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXULAppAPI.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Array.h" // JS::NewArrayObject +#include "js/CallAndConstruct.h" // JS_CallFunctionValue +#include "js/CharacterEncoding.h" +#include "js/CompilationAndEvaluation.h" // JS::Evaluate +#include "js/ContextOptions.h" +#include "js/Printf.h" +#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineFunctions, JS_DefineProperty +#include "js/PropertySpec.h" +#include "js/SourceText.h" // JS::SourceText +#include "mozilla/ChaosMode.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Preferences.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsExceptionHandler.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsIDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nscore.h" +#include "nsArrayEnumerator.h" +#include "nsCOMArray.h" +#include "nsDirectoryServiceUtils.h" +#include "nsCOMPtr.h" +#include "nsJSPrincipals.h" +#include "nsJSUtils.h" +#include "xpcpublic.h" +#include "xpcprivate.h" +#include "BackstagePass.h" +#include "nsIScriptSecurityManager.h" +#include "nsIPrincipal.h" +#include "nsJSUtils.h" + +#include "nsIXULRuntime.h" +#include "nsIAppStartup.h" +#include "Components.h" +#include "ProfilerControl.h" + +#ifdef ANDROID +# include <android/log.h> +# include "XREShellData.h" +#endif + +#ifdef XP_WIN +# include "mozilla/mscom/ProcessRuntime.h" +# include "mozilla/ScopeExit.h" +# include "mozilla/widget/AudioSession.h" +# include "mozilla/WinDllServices.h" +# include "mozilla/WindowsBCryptInitialization.h" +# include <windows.h> +# if defined(MOZ_SANDBOX) +# include "XREShellData.h" +# include "sandboxBroker.h" +# endif +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +// all this crap is needed to do the interactive shell stuff +#include <stdlib.h> +#include <errno.h> +#ifdef HAVE_IO_H +# include <io.h> /* for isatty() */ +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> /* for isatty() */ +#endif + +#ifdef ENABLE_TESTS +# include "xpctest_private.h" +#endif + +// Fuzzing support for XPC runtime fuzzing +#ifdef FUZZING_INTERFACES +# include "xpcrtfuzzing/xpcrtfuzzing.h" +# include "XREShellData.h" +static bool fuzzDoDebug = !!getenv("MOZ_FUZZ_DEBUG"); +static bool fuzzHaveModule = !!getenv("FUZZER"); +#endif // FUZZING_INTERFACES + +using namespace mozilla; +using namespace JS; +using mozilla::dom::AutoEntryScript; +using mozilla::dom::AutoJSAPI; + +class XPCShellDirProvider : public nsIDirectoryServiceProvider2 { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + XPCShellDirProvider() = default; + ~XPCShellDirProvider() = default; + + // The platform resource folder + void SetGREDirs(nsIFile* greDir); + void ClearGREDirs() { + mGREDir = nullptr; + mGREBinDir = nullptr; + } + // The application resource folder + void SetAppDir(nsIFile* appFile); + void ClearAppDir() { mAppDir = nullptr; } + // The app executable + void SetAppFile(nsIFile* appFile); + void ClearAppFile() { mAppFile = nullptr; } + + private: + nsCOMPtr<nsIFile> mGREDir; + nsCOMPtr<nsIFile> mGREBinDir; + nsCOMPtr<nsIFile> mAppDir; + nsCOMPtr<nsIFile> mAppFile; +}; + +#ifdef XP_WIN +class MOZ_STACK_CLASS AutoAudioSession { + public: + AutoAudioSession() { widget::StartAudioSession(); } + + ~AutoAudioSession() { widget::StopAudioSession(); } +}; +#endif + +#define EXITCODE_RUNTIME_ERROR 3 +#define EXITCODE_FILE_NOT_FOUND 4 + +static FILE* gOutFile = nullptr; +static FILE* gErrFile = nullptr; +static FILE* gInFile = nullptr; + +static int gExitCode = 0; +static bool gQuitting = false; +static bool reportWarnings = true; +static bool compileOnly = false; + +static JSPrincipals* gJSPrincipals = nullptr; +static nsAutoString* gWorkingDirectory = nullptr; + +static bool GetLocationProperty(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.thisv().isObject()) { + JS_ReportErrorASCII(cx, "Unexpected this value for GetLocationProperty"); + return false; + } +#if !defined(XP_WIN) && !defined(XP_UNIX) + // XXX: your platform should really implement this + return false; +#else + JS::AutoFilename filename; + if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) { + NS_ConvertUTF8toUTF16 filenameString(filename.get()); + +# if defined(XP_WIN) + // replace forward slashes with backslashes, + // since nsLocalFileWin chokes on them + char16_t* start = filenameString.BeginWriting(); + char16_t* end = filenameString.EndWriting(); + + while (start != end) { + if (*start == L'/') { + *start = L'\\'; + } + start++; + } +# endif + + nsCOMPtr<nsIFile> location; + nsresult rv = + NS_NewLocalFile(filenameString, false, getter_AddRefs(location)); + + if (!location && gWorkingDirectory) { + // could be a relative path, try appending it to the cwd + // and then normalize + nsAutoString absolutePath(*gWorkingDirectory); + absolutePath.Append(filenameString); + + rv = NS_NewLocalFile(absolutePath, false, getter_AddRefs(location)); + } + + if (location) { + bool symlink; + // don't normalize symlinks, because that's kind of confusing + if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && !symlink) + location->Normalize(); + RootedObject locationObj(cx); + RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); + rv = nsXPConnect::XPConnect()->WrapNative( + cx, scope, location, NS_GET_IID(nsIFile), locationObj.address()); + if (NS_SUCCEEDED(rv) && locationObj) { + args.rval().setObject(*locationObj); + } + } + } + + return true; +#endif +} + +static bool GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) { + fputs(prompt, gOutFile); + fflush(gOutFile); + + char line[4096] = {'\0'}; + while (true) { + if (fgets(line, sizeof line, file)) { + strcpy(bufp, line); + return true; + } + if (errno != EINTR) { + return false; + } + } +} + +static bool ReadLine(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // While 4096 might be quite arbitrary, this is something to be fixed in + // bug 105707. It is also the same limit as in ProcessFile. + char buf[4096]; + RootedString str(cx); + + /* If a prompt was specified, construct the string */ + if (args.length() > 0) { + str = JS::ToString(cx, args[0]); + if (!str) { + return false; + } + } else { + str = JS_GetEmptyString(cx); + } + + /* Get a line from the infile */ + JS::UniqueChars strBytes = JS_EncodeStringToLatin1(cx, str); + if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.get())) { + return false; + } + + /* Strip newline character added by GetLine() */ + unsigned int buflen = strlen(buf); + if (buflen == 0) { + if (feof(gInFile)) { + args.rval().setNull(); + return true; + } + } else if (buf[buflen - 1] == '\n') { + --buflen; + } + + /* Turn buf into a JSString */ + str = JS_NewStringCopyN(cx, buf, buflen); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +static bool Print(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + +#ifdef FUZZING_INTERFACES + if (fuzzHaveModule && !fuzzDoDebug) { + // When fuzzing and not debugging, suppress any print() output, + // as it slows down fuzzing and makes libFuzzer's output hard + // to read. + return true; + } +#endif // FUZZING_INTERFACES + + RootedString str(cx); + nsAutoCString utf8output; + + for (unsigned i = 0; i < args.length(); i++) { + str = ToString(cx, args[i]); + if (!str) { + return false; + } + + JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str); + if (!utf8str) { + return false; + } + + if (i) { + utf8output.Append(' '); + } + utf8output.Append(utf8str.get(), strlen(utf8str.get())); + } + utf8output.Append('\n'); + fputs(utf8output.get(), gOutFile); + fflush(gOutFile); + return true; +} + +static bool Dump(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + + if (!args.length()) { + return true; + } + + RootedString str(cx, ToString(cx, args[0])); + if (!str) { + return false; + } + + JS::UniqueChars utf8str = JS_EncodeStringToUTF8(cx, str); + if (!utf8str) { + return false; + } + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.get()); +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsAutoJSString wstr; + if (!wstr.init(cx, str)) { + return false; + } + OutputDebugStringW(wstr.get()); + } +#endif + fputs(utf8str.get(), gOutFile); + fflush(gOutFile); + return true; +} + +static bool Load(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + JS::RootedObject thisObject(cx); + if (!args.computeThis(cx, &thisObject)) { + return false; + } + if (!JS_IsGlobalObject(thisObject)) { + JS_ReportErrorASCII(cx, "Trying to load() into a non-global object"); + return false; + } + + RootedString str(cx); + for (unsigned i = 0; i < args.length(); i++) { + str = ToString(cx, args[i]); + if (!str) { + return false; + } + JS::UniqueChars filename = JS_EncodeStringToUTF8(cx, str); + if (!filename) { + return false; + } + JS::CompileOptions options(cx); + options.setIsRunOnce(true).setSkipFilenameValidation(true); + JS::Rooted<JSScript*> script( + cx, JS::CompileUtf8Path(cx, options, filename.get())); + if (!script) { + return false; + } + + if (!compileOnly) { + if (!JS_ExecuteScript(cx, script)) { + return false; + } + } + } + args.rval().setUndefined(); + return true; +} + +static bool Quit(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + gExitCode = 0; + if (!ToInt32(cx, args.get(0), &gExitCode)) { + return false; + } + + gQuitting = true; + // exit(0); + return false; +} + +static bool DumpXPC(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + + uint16_t depth = 2; + if (args.length() > 0) { + if (!JS::ToUint16(cx, args[0], &depth)) { + return false; + } + } + + nsXPConnect::XPConnect()->DebugDump(int16_t(depth)); + args.rval().setUndefined(); + return true; +} + +static bool GC(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + JS_GC(cx); + + args.rval().setUndefined(); + return true; +} + +#ifdef JS_GC_ZEAL +static bool GCZeal(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + uint32_t zeal; + if (!ToUint32(cx, args.get(0), &zeal)) { + return false; + } + + JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ); + args.rval().setUndefined(); + return true; +} +#endif + +static bool SendCommand(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) { + JS_ReportErrorASCII(cx, "Function takes at least one argument!"); + return false; + } + + RootedString str(cx, ToString(cx, args[0])); + if (!str) { + JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!"); + return false; + } + + if (args.get(1).isObject() && !JS_ObjectIsFunction(&args[1].toObject())) { + JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!"); + return false; + } + + if (!XRE_SendTestShellCommand( + cx, str, args.length() > 1 ? args[1].address() : nullptr)) { + JS_ReportErrorASCII(cx, "Couldn't send command!"); + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool Options(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + ContextOptions oldContextOptions = ContextOptionsRef(cx); + + RootedString str(cx); + JS::UniqueChars opt; + for (unsigned i = 0; i < args.length(); ++i) { + str = ToString(cx, args[i]); + if (!str) { + return false; + } + + opt = JS_EncodeStringToUTF8(cx, str); + if (!opt) { + return false; + } + + if (strcmp(opt.get(), "strict_mode") == 0) { + ContextOptionsRef(cx).toggleStrictMode(); + } else { + JS_ReportErrorUTF8(cx, + "unknown option name '%s'. The valid name is " + "strict_mode.", + opt.get()); + return false; + } + } + + UniqueChars names; + if (names && oldContextOptions.strictMode()) { + names = JS_sprintf_append(std::move(names), "%s%s", names ? "," : "", + "strict_mode"); + if (!names) { + JS_ReportOutOfMemory(cx); + return false; + } + } + + str = JS_NewStringCopyZ(cx, names.get()); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +static PersistentRootedValue* sScriptedInterruptCallback = nullptr; + +static bool XPCShellInterruptCallback(JSContext* cx) { + MOZ_ASSERT(sScriptedInterruptCallback->initialized()); + RootedValue callback(cx, *sScriptedInterruptCallback); + + // If no interrupt callback was set by script, no-op. + if (callback.isUndefined()) { + return true; + } + + MOZ_ASSERT(js::IsFunctionObject(&callback.toObject())); + + JSAutoRealm ar(cx, &callback.toObject()); + RootedValue rv(cx); + if (!JS_CallFunctionValue(cx, nullptr, callback, + JS::HandleValueArray::empty(), &rv) || + !rv.isBoolean()) { + NS_WARNING("Scripted interrupt callback failed! Terminating script."); + JS_ClearPendingException(cx); + return false; + } + + return rv.toBoolean(); +} + +static bool SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) { + MOZ_ASSERT(sScriptedInterruptCallback->initialized()); + + // Sanity-check args. + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + + // Allow callers to remove the interrupt callback by passing undefined. + if (args[0].isUndefined()) { + *sScriptedInterruptCallback = UndefinedValue(); + return true; + } + + // Otherwise, we should have a function object. + if (!args[0].isObject() || !js::IsFunctionObject(&args[0].toObject())) { + JS_ReportErrorASCII(cx, "Argument must be a function"); + return false; + } + + *sScriptedInterruptCallback = args[0]; + + return true; +} + +static bool SimulateNoScriptActivity(JSContext* cx, unsigned argc, Value* vp) { + // Sanity-check args. + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) { + JS_ReportErrorASCII(cx, "Expected a positive integer argument"); + return false; + } + + // This mimics mozilla::SpinEventLoopUntil but instead of spinning the + // event loop we sleep, to make sure we don't run script. + xpc::AutoScriptActivity asa(false); + PR_Sleep(PR_SecondsToInterval(args[0].toInt32())); + + args.rval().setUndefined(); + return true; +} + +static bool RegisterAppManifest(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + if (!args[0].isObject()) { + JS_ReportErrorASCII(cx, + "Expected object as argument 1 to registerAppManifest"); + return false; + } + + Rooted<JSObject*> arg1(cx, &args[0].toObject()); + nsCOMPtr<nsIFile> file; + nsresult rv = nsXPConnect::XPConnect()->WrapJS(cx, arg1, NS_GET_IID(nsIFile), + getter_AddRefs(file)); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + rv = XRE_AddManifestLocation(NS_APP_LOCATION, file); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + return true; +} + +#ifdef ANDROID +static bool ChangeTestShellDir(JSContext* cx, unsigned argc, Value* vp) { + // This method should only be used by testing/xpcshell/head.js to change to + // the correct directory on Android Remote XPCShell tests. + // + // TODO: Bug 1801725 - Find a more ergonomic way to do this than exposing + // identical methods in XPCShellEnvironment and XPCShellImpl to chdir on + // android for Remote XPCShell tests on Android. + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 1) { + JS_ReportErrorASCII(cx, "changeTestShellDir() takes one argument"); + return false; + } + + nsAutoJSCString path; + if (!path.init(cx, args[0])) { + JS_ReportErrorASCII( + cx, "changeTestShellDir(): could not convert argument 1 to string"); + return false; + } + + if (chdir(path.get())) { + JS_ReportErrorASCII(cx, "changeTestShellDir(): could not change directory"); + return false; + } + + return true; +} +#endif + +#ifdef ENABLE_TESTS +static bool RegisterXPCTestComponents(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 0) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + nsresult rv = xpcTestRegisterComponents(); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + return true; +} +#endif + +static const JSFunctionSpec glob_functions[] = { + // clang-format off + JS_FN("print", Print, 0,0), + JS_FN("readline", ReadLine, 1,0), + JS_FN("load", Load, 1,0), + JS_FN("quit", Quit, 0,0), + JS_FN("dumpXPC", DumpXPC, 1,0), + JS_FN("dump", Dump, 1,0), + JS_FN("gc", GC, 0,0), +#ifdef JS_GC_ZEAL + JS_FN("gczeal", GCZeal, 1,0), +#endif + JS_FN("options", Options, 0,0), + JS_FN("sendCommand", SendCommand, 1,0), + JS_FN("atob", xpc::Atob, 1,0), + JS_FN("btoa", xpc::Btoa, 1,0), + JS_FN("setInterruptCallback", SetInterruptCallback, 1,0), + JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity, 1,0), + JS_FN("registerAppManifest", RegisterAppManifest, 1, 0), +#ifdef ANDROID + JS_FN("changeTestShellDir", ChangeTestShellDir, 1,0), +#endif +#ifdef ENABLE_TESTS + JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0), +#endif + JS_FS_END + // clang-format on +}; + +/***************************************************************************/ + +typedef enum JSShellErrNum { +#define MSG_DEF(name, number, count, exception, format) name = number, +#include "jsshell.msg" +#undef MSG_DEF + JSShellErr_Limit +} JSShellErrNum; + +static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { +#define MSG_DEF(name, number, count, exception, format) {#name, format, count}, +#include "jsshell.msg" +#undef MSG_DEF +}; + +static const JSErrorFormatString* my_GetErrorMessage( + void* userRef, const unsigned errorNumber) { + if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) { + return nullptr; + } + + return &jsShell_ErrorFormatString[errorNumber]; +} + +static bool ProcessUtf8Line(AutoJSAPI& jsapi, const char* buffer, + int startline) { + JSContext* cx = jsapi.cx(); + JS::CompileOptions options(cx); + options.setFileAndLine("typein", startline) + .setIsRunOnce(true) + .setSkipFilenameValidation(true); + + JS::SourceText<mozilla::Utf8Unit> srcBuf; + if (!srcBuf.init(cx, buffer, strlen(buffer), JS::SourceOwnership::Borrowed)) { + return false; + } + + JS::RootedScript script(cx, JS::Compile(cx, options, srcBuf)); + if (!script) { + return false; + } + if (compileOnly) { + return true; + } + + JS::RootedValue result(cx); + if (!JS_ExecuteScript(cx, script, &result)) { + return false; + } + + if (result.isUndefined()) { + return true; + } + + RootedString str(cx, JS::ToString(cx, result)); + if (!str) { + return false; + } + + JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, str); + if (!bytes) { + return false; + } + + fprintf(gOutFile, "%s\n", bytes.get()); + return true; +} + +static bool ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file, + bool forceTTY) { + JSContext* cx = jsapi.cx(); + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + MOZ_ASSERT(global); + + if (forceTTY) { + file = stdin; + } else if (!isatty(fileno(file))) { + /* + * It's not interactive - just execute it. + * + * Support the UNIX #! shell hack; gobble the first line if it starts + * with '#'. + */ + int ch = fgetc(file); + if (ch == '#') { + while ((ch = fgetc(file)) != EOF) { + if (ch == '\n' || ch == '\r') { + break; + } + } + } + ungetc(ch, file); + + JS::UniqueChars filenameUtf8 = JS::EncodeNarrowToUtf8(jsapi.cx(), filename); + if (!filenameUtf8) { + return false; + } + + JS::RootedScript script(cx); + JS::RootedValue unused(cx); + JS::CompileOptions options(cx); + options.setFileAndLine(filenameUtf8.get(), 1) + .setIsRunOnce(true) + .setNoScriptRval(true) + .setSkipFilenameValidation(true); + script = JS::CompileUtf8File(cx, options, file); + if (!script) { + return false; + } + return compileOnly || JS_ExecuteScript(cx, script, &unused); + } + + /* It's an interactive filehandle; drop into read-eval-print loop. */ + int lineno = 1; + bool hitEOF = false; + do { + char buffer[4096]; + char* bufp = buffer; + *bufp = '\0'; + + /* + * Accumulate lines until we get a 'compilable unit' - one that either + * generates an error (before running out of source) or that compiles + * cleanly. This should be whenever we get a complete statement that + * coincides with the end of a line. + */ + int startline = lineno; + do { + if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { + hitEOF = true; + break; + } + bufp += strlen(bufp); + lineno++; + } while ( + !JS_Utf8BufferIsCompilableUnit(cx, global, buffer, strlen(buffer))); + + if (!ProcessUtf8Line(jsapi, buffer, startline)) { + jsapi.ReportException(); + } + } while (!hitEOF && !gQuitting); + + fprintf(gOutFile, "\n"); + return true; +} + +static bool Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) { + FILE* file; + + if (forceTTY || !filename || strcmp(filename, "-") == 0) { + file = stdin; + } else { + file = fopen(filename, "r"); + if (!file) { + /* + * Use Latin1 variant here because the encoding of the return value + * of strerror function can be non-UTF-8. + */ + JS_ReportErrorNumberLatin1(jsapi.cx(), my_GetErrorMessage, nullptr, + JSSMSG_CANT_OPEN, filename, strerror(errno)); + gExitCode = EXITCODE_FILE_NOT_FOUND; + return false; + } + } + + bool ok = ProcessFile(jsapi, filename, file, forceTTY); + if (file != stdin) { + fclose(file); + } + return ok; +} + +static int usage() { + fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); + fprintf( + gErrFile, + "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCmIp] " + "[-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n"); + return 2; +} + +static bool printUsageAndSetExitCode() { + gExitCode = usage(); + return false; +} + +static bool ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc, + XPCShellDirProvider* aDirProvider) { + JSContext* cx = jsapi.cx(); + const char rcfilename[] = "xpcshell.js"; + FILE* rcfile; + int rootPosition; + JS::Rooted<JSObject*> argsObj(cx); + char* filename = nullptr; + bool isInteractive = true; + bool forceTTY = false; + + rcfile = fopen(rcfilename, "r"); + if (rcfile) { + printf("[loading '%s'...]\n", rcfilename); + bool ok = ProcessFile(jsapi, rcfilename, rcfile, false); + fclose(rcfile); + if (!ok) { + return false; + } + } + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + + /* + * Scan past all optional arguments so we can create the arguments object + * before processing any -f options, which must interleave properly with + * -v and -w options. This requires two passes, and without getopt, we'll + * have to keep the option logic here and in the second for loop in sync. + * First of all, find out the first argument position which will be passed + * as a script file to be executed. + */ + for (rootPosition = 0; rootPosition < argc; rootPosition++) { + if (argv[rootPosition][0] != '-' || argv[rootPosition][1] == '\0') { + ++rootPosition; + break; + } + + bool isPairedFlag = + argv[rootPosition][0] != '\0' && + (argv[rootPosition][1] == 'v' || argv[rootPosition][1] == 'f' || + argv[rootPosition][1] == 'e'); + if (isPairedFlag && rootPosition < argc - 1) { + ++rootPosition; // Skip over the 'foo' portion of |-v foo|, |-f foo|, or + // |-e foo|. + } + } + + /* + * Create arguments early and define it to root it, so it's safe from any + * GC calls nested below, and so it is available to -f <file> arguments. + */ + argsObj = JS::NewArrayObject(cx, 0); + if (!argsObj) { + return 1; + } + if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) { + return 1; + } + + for (int j = 0, length = argc - rootPosition; j < length; j++) { + RootedString str(cx, JS_NewStringCopyZ(cx, argv[rootPosition++])); + if (!str || !JS_DefineElement(cx, argsObj, j, str, JSPROP_ENUMERATE)) { + return 1; + } + } + + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') { + filename = argv[i++]; + isInteractive = false; + break; + } + switch (argv[i][1]) { + case 'W': + reportWarnings = false; + break; + case 'w': + reportWarnings = true; + break; + case 'x': + break; + case 'd': + /* This used to try to turn on the debugger. */ + break; + case 'm': + break; + case 'f': + if (++i == argc) { + return printUsageAndSetExitCode(); + } + if (!Process(jsapi, argv[i], false)) { + return false; + } + /* + * XXX: js -f foo.js should interpret foo.js and then + * drop into interactive mode, but that breaks test + * harness. Just execute foo.js for now. + */ + isInteractive = false; + break; + case 'i': + isInteractive = forceTTY = true; + break; + case 'e': { + RootedValue rval(cx); + + if (++i == argc) { + return printUsageAndSetExitCode(); + } + + JS::CompileOptions opts(cx); + opts.setSkipFilenameValidation(true); + opts.setFileAndLine("-e", 1); + + JS::SourceText<mozilla::Utf8Unit> srcBuf; + if (srcBuf.init(cx, argv[i], strlen(argv[i]), + JS::SourceOwnership::Borrowed)) { + JS::Evaluate(cx, opts, srcBuf, &rval); + } + + isInteractive = false; + break; + } + case 'C': + compileOnly = true; + isInteractive = false; + break; + default: + return printUsageAndSetExitCode(); + } + } + + if (filename || isInteractive) { + return Process(jsapi, filename, forceTTY); + } + return true; +} + +/***************************************************************************/ + +static bool GetCurrentWorkingDirectory(nsAString& workingDirectory) { +#if !defined(XP_WIN) && !defined(XP_UNIX) + // XXX: your platform should really implement this + return false; +#elif XP_WIN + DWORD requiredLength = GetCurrentDirectoryW(0, nullptr); + workingDirectory.SetLength(requiredLength); + GetCurrentDirectoryW(workingDirectory.Length(), + (LPWSTR)workingDirectory.BeginWriting()); + // we got a trailing null there + workingDirectory.SetLength(requiredLength); + workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\'); +#elif defined(XP_UNIX) + nsAutoCString cwd; + // 1024 is just a guess at a sane starting value + size_t bufsize = 1024; + char* result = nullptr; + while (result == nullptr) { + cwd.SetLength(bufsize); + result = getcwd(cwd.BeginWriting(), cwd.Length()); + if (!result) { + if (errno != ERANGE) { + return false; + } + // need to make the buffer bigger + bufsize *= 2; + } + } + // size back down to the actual string length + cwd.SetLength(strlen(result) + 1); + cwd.Replace(cwd.Length() - 1, 1, '/'); + CopyUTF8toUTF16(cwd, workingDirectory); +#endif + return true; +} + +static JSSecurityCallbacks shellSecurityCallbacks; + +int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData) { + MOZ_ASSERT(aShellData); + + JSContext* cx; + int result = 0; + nsresult rv; + +#ifdef ANDROID + gOutFile = aShellData->outFile; + gErrFile = aShellData->errFile; +#else + gOutFile = stdout; + gErrFile = stderr; +#endif + gInFile = stdin; + + NS_LogInit(); + + mozilla::LogModule::Init(argc, argv); + + // This guard ensures that all threads that attempt to register themselves + // with the IOInterposer will be properly tracked. + mozilla::IOInterposerInit ioInterposerGuard; + + XRE_InitCommandLine(argc, argv); + + char aLocal; + profiler_init(&aLocal); + +#ifdef MOZ_ASAN_REPORTER + PR_SetEnv("MOZ_DISABLE_ASAN_REPORTER=1"); +#endif + + if (PR_GetEnv("MOZ_CHAOSMODE")) { + ChaosFeature feature = ChaosFeature::Any; + long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16); + if (featureInt) { + // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature. + feature = static_cast<ChaosFeature>(featureInt); + } + ChaosMode::SetChaosFeature(feature); + } + + if (ChaosMode::isActive(ChaosFeature::Any)) { + printf_stderr( + "*** You are running in chaos test mode. See ChaosMode.h. ***\n"); + } + +#ifdef XP_WIN + // Some COM settings are global to the process and must be set before any non- + // trivial COM is run in the application. Since these settings may affect + // stability, we should instantiate COM ASAP so that we can ensure that these + // global settings are configured before anything can interfere. + mscom::ProcessRuntime mscom; +#endif + + // The provider needs to outlive the call to shutting down XPCOM. + XPCShellDirProvider dirprovider; + + { // Start scoping nsCOMPtrs + nsCOMPtr<nsIFile> appFile; + rv = XRE_GetBinaryPath(getter_AddRefs(appFile)); + if (NS_FAILED(rv)) { + printf("Couldn't find application file.\n"); + return 1; + } + nsCOMPtr<nsIFile> appDir; + rv = appFile->GetParent(getter_AddRefs(appDir)); + if (NS_FAILED(rv)) { + printf("Couldn't get application directory.\n"); + return 1; + } + + dirprovider.SetAppFile(appFile); + + nsCOMPtr<nsIFile> greDir; + if (argc > 1 && !strcmp(argv[1], "-g")) { + if (argc < 3) { + return usage(); + } + + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + printf("Couldn't use given GRE dir.\n"); + return 1; + } + + dirprovider.SetGREDirs(greDir); + + argc -= 2; + argv += 2; + } else { +#ifdef XP_MACOSX + // On OSX, the GreD needs to point to Contents/Resources in the .app + // bundle. Libraries will be loaded at a relative path to GreD, i.e. + // ../MacOS. + nsCOMPtr<nsIFile> tmpDir; + XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir)); + greDir->GetParent(getter_AddRefs(tmpDir)); + tmpDir->Clone(getter_AddRefs(greDir)); + tmpDir->SetNativeLeafName("Resources"_ns); + bool dirExists = false; + tmpDir->Exists(&dirExists); + if (dirExists) { + greDir = tmpDir.forget(); + } + dirprovider.SetGREDirs(greDir); +#else + nsAutoString workingDir; + if (!GetCurrentWorkingDirectory(workingDir)) { + printf("GetCurrentWorkingDirectory failed.\n"); + return 1; + } + rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + printf("NS_NewLocalFile failed.\n"); + return 1; + } +#endif + } + + if (argc > 1 && !strcmp(argv[1], "-a")) { + if (argc < 3) { + return usage(); + } + + nsCOMPtr<nsIFile> dir; + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir)); + if (NS_SUCCEEDED(rv)) { + appDir = dir; + dirprovider.SetAppDir(appDir); + } + if (NS_FAILED(rv)) { + printf("Couldn't use given appdir.\n"); + return 1; + } + argc -= 2; + argv += 2; + } + + while (argc > 1 && !strcmp(argv[1], "-r")) { + if (argc < 3) { + return usage(); + } + + nsCOMPtr<nsIFile> lf; + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf)); + if (NS_FAILED(rv)) { + printf("Couldn't get manifest file.\n"); + return 1; + } + XRE_AddManifestLocation(NS_APP_LOCATION, lf); + + argc -= 2; + argv += 2; + } + + const char* val = getenv("MOZ_CRASHREPORTER"); + if (val && *val && !CrashReporter::IsDummy()) { + rv = CrashReporter::SetExceptionHandler(greDir, true); + if (NS_FAILED(rv)) { + printf("CrashReporter::SetExceptionHandler failed!\n"); + return 1; + } + MOZ_ASSERT(CrashReporter::GetEnabled()); + } + + if (argc > 1 && !strcmp(argv[1], "--greomni")) { + nsCOMPtr<nsIFile> greOmni; + XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni)); + XRE_InitOmnijar(greOmni, greOmni); + argc -= 2; + argv += 2; + } + + rv = NS_InitXPCOM(nullptr, appDir, &dirprovider); + if (NS_FAILED(rv)) { + printf("NS_InitXPCOM failed!\n"); + return 1; + } + + // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors + // to stderr. + Preferences::SetBool("browser.dom.window.dump.enabled", true); + Preferences::SetBool("devtools.console.stdout.chrome", true); + + AutoJSAPI jsapi; + jsapi.Init(); + cx = jsapi.cx(); + + // Override the default XPConnect interrupt callback. We could store the + // old one and restore it before shutting down, but there's not really a + // reason to bother. + sScriptedInterruptCallback = new PersistentRootedValue; + sScriptedInterruptCallback->init(cx, UndefinedValue()); + + JS_AddInterruptCallback(cx, XPCShellInterruptCallback); + + argc--; + argv++; + + nsCOMPtr<nsIPrincipal> systemprincipal; + // Fetch the system principal and store it away in a global, to use for + // script compilation in Load() and ProcessFile() (including interactive + // eval loop) + { + nsCOMPtr<nsIScriptSecurityManager> securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && securityManager) { + rv = securityManager->GetSystemPrincipal( + getter_AddRefs(systemprincipal)); + if (NS_FAILED(rv)) { + fprintf(gErrFile, + "+++ Failed to obtain SystemPrincipal from " + "ScriptSecurityManager service.\n"); + } else { + // fetch the JS principals and stick in a global + gJSPrincipals = nsJSPrincipals::get(systemprincipal); + JS_HoldPrincipals(gJSPrincipals); + } + } else { + fprintf(gErrFile, + "+++ Failed to get ScriptSecurityManager service, running " + "without principals"); + } + } + + const JSSecurityCallbacks* scb = JS_GetSecurityCallbacks(cx); + MOZ_ASSERT( + scb, + "We are assuming that nsScriptSecurityManager::Init() has been run"); + shellSecurityCallbacks = *scb; + JS_SetSecurityCallbacks(cx, &shellSecurityCallbacks); + + auto backstagePass = MakeRefPtr<BackstagePass>(); + + // Make the default XPCShell global use a fresh zone (rather than the + // System Zone) to improve cross-zone test coverage. + JS::RealmOptions options; + options.creationOptions().setNewCompartmentAndZone(); + xpc::SetPrefableRealmOptions(options); + + // Even if we're building in a configuration where source is + // discarded, there's no reason to do that on XPCShell, and doing so + // might break various automation scripts. + options.behaviors().setDiscardSource(false); + + JS::Rooted<JSObject*> glob(cx); + rv = xpc::InitClassesWithNewWrappedGlobal( + cx, static_cast<nsIGlobalObject*>(backstagePass), systemprincipal, 0, + options, &glob); + if (NS_FAILED(rv)) { + return 1; + } + + // Initialize e10s check on the main thread, if not already done + BrowserTabsRemoteAutostart(); +#if defined(XP_WIN) + // Plugin may require audio session if installed plugin can initialize + // asynchronized. + AutoAudioSession audioSession; + + // Ensure that DLL Services are running + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor(true); + auto dllServicesDisable = + MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); }); + +# if defined(MOZ_SANDBOX) + // Required for sandboxed child processes. + if (aShellData->sandboxBrokerServices) { + SandboxBroker::Initialize(aShellData->sandboxBrokerServices); + SandboxBroker::GeckoDependentInitialize(); + } else { + NS_WARNING( + "Failed to initialize broker services, sandboxed " + "processes will fail to start."); + } +# endif // defined(MOZ_SANDBOX) + + { + DebugOnly<bool> result = WindowsBCryptInitialization(); + MOZ_ASSERT(result); + } +#endif // defined(XP_WIN) + +#ifdef MOZ_CODE_COVERAGE + CodeCoverageHandler::Init(); +#endif + + { + if (!glob) { + return 1; + } + + nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service()); + if (!appStartup) { + return 1; + } + appStartup->DoneStartingUp(); + + backstagePass->SetGlobalObject(glob); + + JSAutoRealm ar(cx, glob); + + if (!JS_InitReflectParse(cx, glob)) { + return 1; + } + + if (!JS_DefineFunctions(cx, glob, glob_functions)) { + return 1; + } + + nsAutoString workingDirectory; + if (GetCurrentWorkingDirectory(workingDirectory)) { + gWorkingDirectory = &workingDirectory; + } + + JS_DefineProperty(cx, glob, "__LOCATION__", GetLocationProperty, nullptr, + 0); + + { +#ifdef FUZZING_INTERFACES + if (fuzzHaveModule) { +# ifdef LIBFUZZER + // argv[0] was removed previously, but libFuzzer expects it + argc++; + argv--; + + result = FuzzXPCRuntimeStart(&jsapi, &argc, &argv, + aShellData->fuzzerDriver); +# elif AFLFUZZ + MOZ_CRASH("AFL is unsupported for XPC runtime fuzzing integration"); +# endif + } else { +#endif + // We are almost certainly going to run script here, so we need an + // AutoEntryScript. This is Gecko-specific and not in any spec. + AutoEntryScript aes(backstagePass, "xpcshell argument processing"); + + // If an exception is thrown, we'll set our return code + // appropriately, and then let the AutoEntryScript destructor report + // the error to the console. + if (!ProcessArgs(aes, argv, argc, &dirprovider)) { + if (gExitCode) { + result = gExitCode; + } else if (gQuitting) { + result = 0; + } else { + result = EXITCODE_RUNTIME_ERROR; + } + } +#ifdef FUZZING_INTERFACES + } +#endif + } + + // Signal that we're now shutting down. + nsCOMPtr<nsIObserver> obs = do_QueryInterface(appStartup); + if (obs) { + obs->Observe(nullptr, "quit-application-forced", nullptr); + } + + JS_DropPrincipals(cx, gJSPrincipals); + JS_SetAllNonReservedSlotsToUndefined(glob); + JS::RootedObject lexicalEnv(cx, JS_GlobalLexicalEnvironment(glob)); + JS_SetAllNonReservedSlotsToUndefined(lexicalEnv); + JS_GC(cx); + } + JS_GC(cx); + + dirprovider.ClearGREDirs(); + dirprovider.ClearAppDir(); + dirprovider.ClearAppFile(); + } // this scopes the nsCOMPtrs + + if (!XRE_ShutdownTestShell()) { + NS_ERROR("problem shutting down testshell"); + } + + // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM + rv = NS_ShutdownXPCOM(nullptr); + MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); + + // Shut down the crashreporter service to prevent leaking some strings it + // holds. + if (CrashReporter::GetEnabled()) { + CrashReporter::UnsetExceptionHandler(); + } + + // This must precede NS_LogTerm(), otherwise xpcshell return non-zero + // during some tests, which causes failures. + profiler_shutdown(); + + NS_LogTerm(); + + XRE_DeinitCommandLine(); + + return result; +} + +void XPCShellDirProvider::SetGREDirs(nsIFile* greDir) { + mGREDir = greDir; + mGREDir->Clone(getter_AddRefs(mGREBinDir)); + +#ifdef XP_MACOSX + nsAutoCString leafName; + mGREDir->GetNativeLeafName(leafName); + if (leafName.EqualsLiteral("Resources")) { + mGREBinDir->SetNativeLeafName("MacOS"_ns); + } +#endif +} + +void XPCShellDirProvider::SetAppFile(nsIFile* appFile) { mAppFile = appFile; } + +void XPCShellDirProvider::SetAppDir(nsIFile* appDir) { mAppDir = appDir; } + +NS_IMETHODIMP_(MozExternalRefCountType) +XPCShellDirProvider::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +XPCShellDirProvider::Release() { return 1; } + +NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +NS_IMETHODIMP +XPCShellDirProvider::GetFile(const char* prop, bool* persistent, + nsIFile** result) { + if (mGREDir && !strcmp(prop, NS_GRE_DIR)) { + *persistent = true; + return mGREDir->Clone(result); + } else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) { + *persistent = true; + return mGREBinDir->Clone(result); + } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) { + *persistent = true; + return mAppFile->Clone(result); + } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) { + nsCOMPtr<nsIFile> file; + *persistent = true; + if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) || + NS_FAILED(file->AppendNative("defaults"_ns)) || + NS_FAILED(file->AppendNative("pref"_ns))) + return NS_ERROR_FAILURE; + file.forget(result); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +XPCShellDirProvider::GetFiles(const char* prop, nsISimpleEnumerator** result) { + if (mGREDir && !strcmp(prop, "ChromeML")) { + nsCOMArray<nsIFile> dirs; + + nsCOMPtr<nsIFile> file; + mGREDir->Clone(getter_AddRefs(file)); + file->AppendNative("chrome"_ns); + dirs.AppendObject(file); + + nsresult rv = + NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + dirs.AppendObject(file); + } + + return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile)); + } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { + nsCOMArray<nsIFile> dirs; + nsCOMPtr<nsIFile> appDir; + bool exists; + if (mAppDir && NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) && + NS_SUCCEEDED(appDir->AppendNative("defaults"_ns)) && + NS_SUCCEEDED(appDir->AppendNative("preferences"_ns)) && + NS_SUCCEEDED(appDir->Exists(&exists)) && exists) { + dirs.AppendObject(appDir); + return NS_NewArrayEnumerator(result, dirs, NS_GET_IID(nsIFile)); + } + return NS_ERROR_FAILURE; + } + return NS_ERROR_FAILURE; +} diff --git a/js/xpconnect/src/XPCString.cpp b/js/xpconnect/src/XPCString.cpp new file mode 100644 index 0000000000..634d7efcbd --- /dev/null +++ b/js/xpconnect/src/XPCString.cpp @@ -0,0 +1,134 @@ +/* -*- 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/. */ + +/* + * Infrastructure for sharing DOMString data with JSStrings. + * + * Importing an nsAString into JS: + * If possible (GetSharedBufferHandle works) use the external string support in + * JS to create a JSString that points to the readable's buffer. We keep a + * reference to the buffer handle until the JSString is finalized. + * + * Exporting a JSString as an nsAReadable: + * Wrap the JSString with a root-holding XPCJSReadableStringWrapper, which roots + * the string and exposes its buffer via the nsAString interface, as + * well as providing refcounting support. + */ + +#include "nscore.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "jsapi.h" +#include "xpcpublic.h" + +using namespace JS; + +const XPCStringConvert::LiteralExternalString + XPCStringConvert::sLiteralExternalString; + +const XPCStringConvert::DOMStringExternalString + XPCStringConvert::sDOMStringExternalString; + +const XPCStringConvert::DynamicAtomExternalString + XPCStringConvert::sDynamicAtomExternalString; + +void XPCStringConvert::LiteralExternalString::finalize(char16_t* aChars) const { + // Nothing to do. +} + +size_t XPCStringConvert::LiteralExternalString::sizeOfBuffer( + const char16_t* aChars, mozilla::MallocSizeOf aMallocSizeOf) const { + // This string's buffer is not heap-allocated, so its malloc size is 0. + return 0; +} + +void XPCStringConvert::DOMStringExternalString::finalize( + char16_t* aChars) const { + nsStringBuffer* buf = nsStringBuffer::FromData(aChars); + buf->Release(); +} + +size_t XPCStringConvert::DOMStringExternalString::sizeOfBuffer( + const char16_t* aChars, mozilla::MallocSizeOf aMallocSizeOf) const { + // We promised the JS engine we would not GC. Enforce that: + JS::AutoCheckCannotGC autoCannotGC; + + const nsStringBuffer* buf = + nsStringBuffer::FromData(const_cast<char16_t*>(aChars)); + // We want sizeof including this, because the entire string buffer is owned by + // the external string. But only report here if we're unshared; if we're + // shared then we don't know who really owns this data. + return buf->SizeOfIncludingThisIfUnshared(aMallocSizeOf); +} + +void XPCStringConvert::DynamicAtomExternalString::finalize( + char16_t* aChars) const { + nsDynamicAtom* atom = nsDynamicAtom::FromChars(aChars); + // nsDynamicAtom::Release is always-inline and defined in a translation unit + // we can't get to here. So we need to go through nsAtom::Release to call + // it. + static_cast<nsAtom*>(atom)->Release(); +} + +size_t XPCStringConvert::DynamicAtomExternalString::sizeOfBuffer( + const char16_t* aChars, mozilla::MallocSizeOf aMallocSizeOf) const { + // We return 0 here because NS_AddSizeOfAtoms reports all memory associated + // with atoms in the atom table. + return 0; +} + +// convert a readable to a JSString, copying string data +// static +bool XPCStringConvert::ReadableToJSVal(JSContext* cx, const nsAString& readable, + nsStringBuffer** sharedBuffer, + MutableHandleValue vp) { + *sharedBuffer = nullptr; + + uint32_t length = readable.Length(); + + if (readable.IsLiteral()) { + return StringLiteralToJSVal(cx, readable.BeginReading(), length, vp); + } + + nsStringBuffer* buf = nsStringBuffer::FromString(readable); + if (buf) { + bool shared; + if (!StringBufferToJSVal(cx, buf, length, vp, &shared)) { + return false; + } + if (shared) { + *sharedBuffer = buf; + } + return true; + } + + // blech, have to copy. + JSString* str = JS_NewUCStringCopyN(cx, readable.BeginReading(), length); + if (!str) { + return false; + } + vp.setString(str); + return true; +} + +namespace xpc { + +bool NonVoidStringToJsval(JSContext* cx, nsAString& str, + MutableHandleValue rval) { + nsStringBuffer* sharedBuffer; + if (!XPCStringConvert::ReadableToJSVal(cx, str, &sharedBuffer, rval)) { + return false; + } + + if (sharedBuffer) { + // The string was shared but ReadableToJSVal didn't addref it. + // Move the ownership from str to jsstr. + str.ForgetSharedBuffer(); + } + return true; +} + +} // namespace xpc diff --git a/js/xpconnect/src/XPCThrower.cpp b/js/xpconnect/src/XPCThrower.cpp new file mode 100644 index 0000000000..bee0a0b0f5 --- /dev/null +++ b/js/xpconnect/src/XPCThrower.cpp @@ -0,0 +1,188 @@ +/* -*- 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/. */ + +/* Code for throwing errors into JavaScript. */ + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "js/CharacterEncoding.h" +#include "js/Printf.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/Exceptions.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::dom; + +bool XPCThrower::sVerbose = true; + +// static +void XPCThrower::Throw(nsresult rv, JSContext* cx) { + const char* format; + if (JS_IsExceptionPending(cx)) { + return; + } + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) { + format = ""; + } + dom::Throw(cx, rv, nsDependentCString(format)); +} + +namespace xpc { + +bool Throw(JSContext* cx, nsresult rv) { + XPCThrower::Throw(rv, cx); + return false; +} + +} // namespace xpc + +/* + * If there has already been an exception thrown, see if we're throwing the + * same sort of exception, and if we are, don't clobber the old one. ccx + * should be the current call context. + */ +// static +bool XPCThrower::CheckForPendingException(nsresult result, JSContext* cx) { + RefPtr<Exception> e = XPCJSContext::Get()->GetPendingException(); + if (!e) { + return false; + } + XPCJSContext::Get()->SetPendingException(nullptr); + + if (e->GetResult() != result) { + return false; + } + + ThrowExceptionObject(cx, e); + return true; +} + +// static +void XPCThrower::Throw(nsresult rv, XPCCallContext& ccx) { + char* sz; + const char* format; + + if (CheckForPendingException(rv, ccx)) { + return; + } + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) { + format = ""; + } + + sz = (char*)format; + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) { + Verbosify(ccx, &sz, false); + } + + dom::Throw(ccx, rv, nsDependentCString(sz)); + + if (sz && sz != format) { + js_free(sz); + } +} + +// static +void XPCThrower::ThrowBadResult(nsresult rv, nsresult result, + XPCCallContext& ccx) { + char* sz; + const char* format; + const char* name; + + /* + * If there is a pending exception when the native call returns and + * it has the same error result as returned by the native call, then + * the native call may be passing through an error from a previous JS + * call. So we'll just throw that exception into our JS. Note that + * we don't need to worry about NS_ERROR_UNCATCHABLE_EXCEPTION, + * because presumably there would be no pending exception for that + * nsresult! + */ + + if (CheckForPendingException(result, ccx)) { + return; + } + + // else... + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format) || + !format) { + format = ""; + } + + if (nsXPCException::NameAndFormatForNSResult(result, &name, nullptr) && + name) { + sz = JS_smprintf("%s 0x%x (%s)", format, (unsigned)result, name).release(); + } else { + sz = JS_smprintf("%s 0x%x", format, (unsigned)result).release(); + } + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) { + Verbosify(ccx, &sz, true); + } + + dom::Throw(ccx, result, nsDependentCString(sz)); + + if (sz) { + js_free(sz); + } +} + +// static +void XPCThrower::ThrowBadParam(nsresult rv, unsigned paramNum, + XPCCallContext& ccx) { + char* sz; + const char* format; + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) { + format = ""; + } + + sz = JS_smprintf("%s arg %d", format, paramNum).release(); + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) { + Verbosify(ccx, &sz, true); + } + + dom::Throw(ccx, rv, nsDependentCString(sz)); + + if (sz) { + js_free(sz); + } +} + +// static +void XPCThrower::Verbosify(XPCCallContext& ccx, char** psz, bool own) { + char* sz = nullptr; + + if (ccx.HasInterfaceAndMember()) { + XPCNativeInterface* iface = ccx.GetInterface(); + jsid id = ccx.GetMember()->GetName(); + const char* name; + JS::UniqueChars bytes; + if (!id.isVoid()) { + bytes = JS_EncodeStringToLatin1(ccx, id.toString()); + name = bytes ? bytes.get() : ""; + } else { + name = "Unknown"; + } + sz = + JS_smprintf("%s [%s.%s]", *psz, iface->GetNameString(), name).release(); + } + + if (sz) { + if (own) { + js_free(*psz); + } + *psz = sz; + } +} diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp new file mode 100644 index 0000000000..73e6279f15 --- /dev/null +++ b/js/xpconnect/src/XPCVariant.cpp @@ -0,0 +1,764 @@ +/* -*- 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/. */ + +/* nsIVariant implementation for xpconnect. */ + +#include "mozilla/Range.h" + +#include "xpcprivate.h" + +#include "jsfriendapi.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit +#include "js/friend/WindowProxy.h" // js::ToWindowIfWindowProxy +#include "js/PropertyAndElement.h" // JS_GetElement +#include "js/Wrapper.h" +#include "mozilla/HoldDropJSObjects.h" + +using namespace JS; +using namespace mozilla; +using namespace xpc; + +NS_IMPL_CLASSINFO(XPCVariant, nullptr, 0, XPCVARIANT_CID) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCVariant) + NS_INTERFACE_MAP_ENTRY(XPCVariant) + NS_INTERFACE_MAP_ENTRY(nsIVariant) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_IMPL_QUERY_CLASSINFO(XPCVariant) +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(XPCVariant, XPCVariant, nsIVariant) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCVariant) +NS_IMPL_CYCLE_COLLECTING_RELEASE(XPCVariant) + +XPCVariant::XPCVariant(JSContext* cx, const Value& aJSVal) : mJSVal(aJSVal) { + if (!mJSVal.isPrimitive()) { + // XXXbholley - The innerization here was from bug 638026. Blake says + // the basic problem was that we were storing the C++ inner but the JS + // outer, which meant that, after navigation, the JS inner could be + // collected, which would cause us to try to recreate the JS inner at + // some later point after teardown, which would crash. This is shouldn't + // be a problem anymore because SetParentToWindow will do the right + // thing, but I'm saving the cleanup here for another day. Blake thinks + // that we should just not store the WN if we're creating a variant for + // an outer window. + JSObject* obj = js::ToWindowIfWindowProxy(&mJSVal.toObject()); + mJSVal = JS::ObjectValue(*obj); + + JSObject* unwrapped = + js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false); + mReturnRawObject = !(unwrapped && IsWrappedNativeReflector(unwrapped)); + } else { + mReturnRawObject = false; + } + + if (aJSVal.isGCThing()) { + mozilla::HoldJSObjects(this); + } +} + +XPCVariant::~XPCVariant() { Cleanup(); } + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPCVariant) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPCVariant) + tmp->mData.Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPCVariant) + tmp->Cleanup(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(XPCVariant) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSVal) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +// static +already_AddRefed<XPCVariant> XPCVariant::newVariant(JSContext* cx, + const Value& aJSVal) { + RefPtr<XPCVariant> variant = new XPCVariant(cx, aJSVal); + if (!variant->InitializeData(cx)) { + return nullptr; + } + + return variant.forget(); +} + +void XPCVariant::Cleanup() { + mData.Cleanup(); + + if (!GetJSValPreserveColor().isGCThing()) { + return; + } + mJSVal = JS::NullValue(); + mozilla::DropJSObjects(this); +} + +// Helper class to give us a namespace for the table based code below. +class XPCArrayHomogenizer { + private: + enum Type { + tNull = 0, // null value + tInt, // Integer + tDbl, // Double + tBool, // Boolean + tStr, // String + tID, // ID + tArr, // Array + tISup, // nsISupports (really just a plain JSObject) + tUnk, // Unknown. Used only for initial state. + + tTypeCount, // Just a count for table dimensioning. + + tVar, // nsVariant - last ditch if no other common type found. + tErr // No valid state or type has this value. + }; + + // Table has tUnk as a state (column) but not as a type (row). + static const Type StateTable[tTypeCount][tTypeCount - 1]; + + public: + static bool GetTypeForArray(JSContext* cx, HandleObject array, + uint32_t length, nsXPTType* resultType, + nsID* resultID); +}; + +// Current state is the column down the side. +// Current type is the row along the top. +// New state is in the box at the intersection. + +const XPCArrayHomogenizer::Type + XPCArrayHomogenizer::StateTable[tTypeCount][tTypeCount - 1] = { + /* tNull,tInt ,tDbl ,tBool,tStr ,tID ,tArr ,tISup */ + /* tNull */ {tNull, tVar, tVar, tVar, tStr, tID, tVar, tISup}, + /* tInt */ {tVar, tInt, tDbl, tVar, tVar, tVar, tVar, tVar}, + /* tDbl */ {tVar, tDbl, tDbl, tVar, tVar, tVar, tVar, tVar}, + /* tBool */ {tVar, tVar, tVar, tBool, tVar, tVar, tVar, tVar}, + /* tStr */ {tStr, tVar, tVar, tVar, tStr, tVar, tVar, tVar}, + /* tID */ {tID, tVar, tVar, tVar, tVar, tID, tVar, tVar}, + /* tArr */ {tErr, tErr, tErr, tErr, tErr, tErr, tErr, tErr}, + /* tISup */ {tISup, tVar, tVar, tVar, tVar, tVar, tVar, tISup}, + /* tUnk */ {tNull, tInt, tDbl, tBool, tStr, tID, tVar, tISup}}; + +// static +bool XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, + uint32_t length, + nsXPTType* resultType, + nsID* resultID) { + Type state = tUnk; + Type type; + + RootedValue val(cx); + RootedObject jsobj(cx); + for (uint32_t i = 0; i < length; i++) { + if (!JS_GetElement(cx, array, i, &val)) { + return false; + } + + if (val.isInt32()) { + type = tInt; + } else if (val.isDouble()) { + type = tDbl; + } else if (val.isBoolean()) { + type = tBool; + } else if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { + state = tVar; + break; + } else if (val.isNull()) { + type = tNull; + } else if (val.isString()) { + type = tStr; + } else { + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); + jsobj = &val.toObject(); + + bool isArray; + if (!JS::IsArrayObject(cx, jsobj, &isArray)) { + return false; + } + + if (isArray) { + type = tArr; + } else if (xpc::JSValue2ID(cx, val)) { + type = tID; + } else { + type = tISup; + } + } + + MOZ_ASSERT(state != tErr, "bad state table!"); + MOZ_ASSERT(type != tErr, "bad type!"); + MOZ_ASSERT(type != tVar, "bad type!"); + MOZ_ASSERT(type != tUnk, "bad type!"); + + state = StateTable[state][type]; + + MOZ_ASSERT(state != tErr, "bad state table!"); + MOZ_ASSERT(state != tUnk, "bad state table!"); + + if (state == tVar) { + break; + } + } + + switch (state) { + case tInt: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::INT32); + break; + case tDbl: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::DOUBLE); + break; + case tBool: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::BOOL); + break; + case tStr: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::PWSTRING); + break; + case tID: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::NSIDPTR); + break; + case tISup: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::INTERFACE_IS_TYPE); + *resultID = NS_GET_IID(nsISupports); + break; + case tNull: + // FALL THROUGH + case tVar: + *resultType = nsXPTType::MkArrayType(nsXPTType::Idx::INTERFACE_IS_TYPE); + *resultID = NS_GET_IID(nsIVariant); + break; + case tArr: + // FALL THROUGH + case tUnk: + // FALL THROUGH + case tErr: + // FALL THROUGH + default: + NS_ERROR("bad state"); + return false; + } + return true; +} + +bool XPCVariant::InitializeData(JSContext* cx) { + js::AutoCheckRecursionLimit recursion(cx); + if (!recursion.check(cx)) { + return false; + } + + RootedValue val(cx, GetJSVal()); + + if (val.isInt32()) { + mData.SetFromInt32(val.toInt32()); + return true; + } + if (val.isDouble()) { + mData.SetFromDouble(val.toDouble()); + return true; + } + if (val.isBoolean()) { + mData.SetFromBool(val.toBoolean()); + return true; + } + // We can't represent symbol or BigInt on C++ side, so pretend it is void. + if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { + mData.SetToVoid(); + return true; + } + if (val.isNull()) { + mData.SetToEmpty(); + return true; + } + if (val.isString()) { + RootedString str(cx, val.toString()); + if (!str) { + return false; + } + + MOZ_ASSERT(mData.GetType() == nsIDataType::VTYPE_EMPTY, + "Why do we already have data?"); + + size_t length = JS_GetStringLength(str); + mData.AllocateWStringWithSize(length); + + mozilla::Range<char16_t> destChars(mData.u.wstr.mWStringValue, length); + if (!JS_CopyStringChars(cx, destChars, str)) { + return false; + } + + MOZ_ASSERT(mData.u.wstr.mWStringValue[length] == '\0'); + return true; + } + if (Maybe<nsID> id = xpc::JSValue2ID(cx, val)) { + mData.SetFromID(id.ref()); + return true; + } + + // leaving only JSObject... + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); + + RootedObject jsobj(cx, &val.toObject()); + + // Let's see if it is a js array object. + + uint32_t len; + + bool isArray; + if (!JS::IsArrayObject(cx, jsobj, &isArray) || + (isArray && !JS::GetArrayLength(cx, jsobj, &len))) { + return false; + } + + if (isArray) { + if (!len) { + // Zero length array + mData.SetToEmptyArray(); + return true; + } + + nsXPTType type; + nsID id; + + if (!XPCArrayHomogenizer::GetTypeForArray(cx, jsobj, len, &type, &id)) { + return false; + } + + if (!XPCConvert::JSData2Native(cx, &mData.u.array.mArrayValue, val, type, + &id, len, nullptr)) + return false; + + const nsXPTType& elty = type.ArrayElementType(); + mData.mType = nsIDataType::VTYPE_ARRAY; + if (elty.IsInterfacePointer()) { + mData.u.array.mArrayInterfaceID = id; + } + mData.u.array.mArrayCount = len; + mData.u.array.mArrayType = elty.Tag(); + + return true; + } + + // XXX This could be smarter and pick some more interesting iface. + + nsIXPConnect* xpc = nsIXPConnect::XPConnect(); + nsCOMPtr<nsISupports> wrapper; + const nsIID& iid = NS_GET_IID(nsISupports); + + if (NS_FAILED(xpc->WrapJS(cx, jsobj, iid, getter_AddRefs(wrapper)))) { + return false; + } + + mData.SetFromInterface(iid, wrapper); + return true; +} + +NS_IMETHODIMP +XPCVariant::GetAsJSVal(MutableHandleValue result) { + result.set(GetJSVal()); + return NS_OK; +} + +// static +bool XPCVariant::VariantDataToJS(JSContext* cx, nsIVariant* variant, + nsresult* pErr, MutableHandleValue pJSVal) { + // Get the type early because we might need to spoof it below. + uint16_t type = variant->GetDataType(); + + RootedValue realVal(cx); + nsresult rv = variant->GetAsJSVal(&realVal); + + if (NS_SUCCEEDED(rv) && + (realVal.isPrimitive() || type == nsIDataType::VTYPE_ARRAY || + type == nsIDataType::VTYPE_EMPTY_ARRAY || + type == nsIDataType::VTYPE_ID)) { + if (!JS_WrapValue(cx, &realVal)) { + return false; + } + pJSVal.set(realVal); + return true; + } + + nsCOMPtr<XPCVariant> xpcvariant = do_QueryInterface(variant); + if (xpcvariant && xpcvariant->mReturnRawObject) { + MOZ_ASSERT(type == nsIDataType::VTYPE_INTERFACE || + type == nsIDataType::VTYPE_INTERFACE_IS, + "Weird variant"); + + if (!JS_WrapValue(cx, &realVal)) { + return false; + } + pJSVal.set(realVal); + return true; + } + + // else, it's an object and we really need to double wrap it if we've + // already decided that its 'natural' type is as some sort of interface. + + // We just fall through to the code below and let it do what it does. + + // The nsIVariant is not a XPCVariant (or we act like it isn't). + // So we extract the data and do the Right Thing. + + // We ASSUME that the variant implementation can do these conversions... + + nsID iid; + + switch (type) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: { + double d; + if (NS_FAILED(variant->GetAsDouble(&d))) { + return false; + } + pJSVal.set(JS_NumberValue(d)); + return true; + } + case nsIDataType::VTYPE_BOOL: { + bool b; + if (NS_FAILED(variant->GetAsBool(&b))) { + return false; + } + pJSVal.setBoolean(b); + return true; + } + case nsIDataType::VTYPE_CHAR: { + char c; + if (NS_FAILED(variant->GetAsChar(&c))) { + return false; + } + return XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&c, {TD_CHAR}, + &iid, 0, pErr); + } + case nsIDataType::VTYPE_WCHAR: { + char16_t wc; + if (NS_FAILED(variant->GetAsWChar(&wc))) { + return false; + } + return XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&wc, {TD_WCHAR}, + &iid, 0, pErr); + } + case nsIDataType::VTYPE_ID: { + if (NS_FAILED(variant->GetAsID(&iid))) { + return false; + } + nsID* v = &iid; + return XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&v, + {TD_NSIDPTR}, &iid, 0, pErr); + } + case nsIDataType::VTYPE_ASTRING: { + nsAutoString astring; + if (NS_FAILED(variant->GetAsAString(astring))) { + return false; + } + return XPCConvert::NativeData2JS(cx, pJSVal, &astring, {TD_ASTRING}, &iid, + 0, pErr); + } + case nsIDataType::VTYPE_CSTRING: { + nsAutoCString cString; + if (NS_FAILED(variant->GetAsACString(cString))) { + return false; + } + return XPCConvert::NativeData2JS(cx, pJSVal, &cString, {TD_CSTRING}, &iid, + 0, pErr); + } + case nsIDataType::VTYPE_UTF8STRING: { + nsUTF8String utf8String; + if (NS_FAILED(variant->GetAsAUTF8String(utf8String))) { + return false; + } + return XPCConvert::NativeData2JS(cx, pJSVal, &utf8String, {TD_UTF8STRING}, + &iid, 0, pErr); + } + case nsIDataType::VTYPE_CHAR_STR: { + char* pc; + if (NS_FAILED(variant->GetAsString(&pc))) { + return false; + } + bool success = XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&pc, + {TD_PSTRING}, &iid, 0, pErr); + free(pc); + return success; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + char* pc; + uint32_t size; + if (NS_FAILED(variant->GetAsStringWithSize(&size, &pc))) { + return false; + } + bool success = XPCConvert::NativeData2JS( + cx, pJSVal, (const void*)&pc, {TD_PSTRING_SIZE_IS}, &iid, size, pErr); + free(pc); + return success; + } + case nsIDataType::VTYPE_WCHAR_STR: { + char16_t* pwc; + if (NS_FAILED(variant->GetAsWString(&pwc))) { + return false; + } + bool success = XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&pwc, + {TD_PSTRING}, &iid, 0, pErr); + free(pwc); + return success; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + char16_t* pwc; + uint32_t size; + if (NS_FAILED(variant->GetAsWStringWithSize(&size, &pwc))) { + return false; + } + bool success = + XPCConvert::NativeData2JS(cx, pJSVal, (const void*)&pwc, + {TD_PWSTRING_SIZE_IS}, &iid, size, pErr); + free(pwc); + return success; + } + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: { + nsISupports* pi; + nsID* piid; + if (NS_FAILED(variant->GetAsInterface(&piid, (void**)&pi))) { + return false; + } + + iid = *piid; + free((char*)piid); + + bool success = XPCConvert::NativeData2JS( + cx, pJSVal, (const void*)&pi, {TD_INTERFACE_IS_TYPE}, &iid, 0, pErr); + if (pi) { + pi->Release(); + } + return success; + } + case nsIDataType::VTYPE_ARRAY: { + nsDiscriminatedUnion du; + nsresult rv; + + rv = variant->GetAsArray( + &du.u.array.mArrayType, &du.u.array.mArrayInterfaceID, + &du.u.array.mArrayCount, &du.u.array.mArrayValue); + if (NS_FAILED(rv)) { + return false; + } + + // must exit via VARIANT_DONE from here on... + du.mType = nsIDataType::VTYPE_ARRAY; + + uint16_t elementType = du.u.array.mArrayType; + const nsID* pid = nullptr; + + nsXPTType::Idx xptIndex; + switch (elementType) { + case nsIDataType::VTYPE_INT8: + xptIndex = nsXPTType::Idx::INT8; + break; + case nsIDataType::VTYPE_INT16: + xptIndex = nsXPTType::Idx::INT16; + break; + case nsIDataType::VTYPE_INT32: + xptIndex = nsXPTType::Idx::INT32; + break; + case nsIDataType::VTYPE_INT64: + xptIndex = nsXPTType::Idx::INT64; + break; + case nsIDataType::VTYPE_UINT8: + xptIndex = nsXPTType::Idx::UINT8; + break; + case nsIDataType::VTYPE_UINT16: + xptIndex = nsXPTType::Idx::UINT16; + break; + case nsIDataType::VTYPE_UINT32: + xptIndex = nsXPTType::Idx::UINT32; + break; + case nsIDataType::VTYPE_UINT64: + xptIndex = nsXPTType::Idx::UINT64; + break; + case nsIDataType::VTYPE_FLOAT: + xptIndex = nsXPTType::Idx::FLOAT; + break; + case nsIDataType::VTYPE_DOUBLE: + xptIndex = nsXPTType::Idx::DOUBLE; + break; + case nsIDataType::VTYPE_BOOL: + xptIndex = nsXPTType::Idx::BOOL; + break; + case nsIDataType::VTYPE_CHAR: + xptIndex = nsXPTType::Idx::CHAR; + break; + case nsIDataType::VTYPE_WCHAR: + xptIndex = nsXPTType::Idx::WCHAR; + break; + case nsIDataType::VTYPE_ID: + xptIndex = nsXPTType::Idx::NSIDPTR; + break; + case nsIDataType::VTYPE_CHAR_STR: + xptIndex = nsXPTType::Idx::PSTRING; + break; + case nsIDataType::VTYPE_WCHAR_STR: + xptIndex = nsXPTType::Idx::PWSTRING; + break; + case nsIDataType::VTYPE_INTERFACE: + pid = &NS_GET_IID(nsISupports); + xptIndex = nsXPTType::Idx::INTERFACE_IS_TYPE; + break; + case nsIDataType::VTYPE_INTERFACE_IS: + pid = &du.u.array.mArrayInterfaceID; + xptIndex = nsXPTType::Idx::INTERFACE_IS_TYPE; + break; + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + return false; + } + + bool success = XPCConvert::NativeData2JS( + cx, pJSVal, (const void*)&du.u.array.mArrayValue, + nsXPTType::MkArrayType(xptIndex), pid, du.u.array.mArrayCount, pErr); + + return success; + } + case nsIDataType::VTYPE_EMPTY_ARRAY: { + JSObject* array = JS::NewArrayObject(cx, 0); + if (!array) { + return false; + } + pJSVal.setObject(*array); + return true; + } + case nsIDataType::VTYPE_VOID: + pJSVal.setUndefined(); + return true; + case nsIDataType::VTYPE_EMPTY: + pJSVal.setNull(); + return true; + default: + NS_ERROR("bad type in variant!"); + return false; + } +} + +/***************************************************************************/ +/***************************************************************************/ +// XXX These default implementations need to be improved to allow for +// some more interesting conversions. + +uint16_t XPCVariant::GetDataType() { return mData.GetType(); } + +NS_IMETHODIMP XPCVariant::GetAsInt8(uint8_t* _retval) { + return mData.ConvertToInt8(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt16(int16_t* _retval) { + return mData.ConvertToInt16(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt32(int32_t* _retval) { + return mData.ConvertToInt32(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt64(int64_t* _retval) { + return mData.ConvertToInt64(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint8(uint8_t* _retval) { + return mData.ConvertToUint8(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint16(uint16_t* _retval) { + return mData.ConvertToUint16(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint32(uint32_t* _retval) { + return mData.ConvertToUint32(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint64(uint64_t* _retval) { + return mData.ConvertToUint64(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsFloat(float* _retval) { + return mData.ConvertToFloat(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsDouble(double* _retval) { + return mData.ConvertToDouble(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsBool(bool* _retval) { + return mData.ConvertToBool(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsChar(char* _retval) { + return mData.ConvertToChar(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsWChar(char16_t* _retval) { + return mData.ConvertToWChar(_retval); +} + +NS_IMETHODIMP_(nsresult) XPCVariant::GetAsID(nsID* retval) { + return mData.ConvertToID(retval); +} + +NS_IMETHODIMP XPCVariant::GetAsAString(nsAString& _retval) { + return mData.ConvertToAString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsACString(nsACString& _retval) { + return mData.ConvertToACString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsAUTF8String(nsAUTF8String& _retval) { + return mData.ConvertToAUTF8String(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsString(char** _retval) { + return mData.ConvertToString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsWString(char16_t** _retval) { + return mData.ConvertToWString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsISupports(nsISupports** _retval) { + return mData.ConvertToISupports(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInterface(nsIID** iid, void** iface) { + return mData.ConvertToInterface(iid, iface); +} + +NS_IMETHODIMP_(nsresult) +XPCVariant::GetAsArray(uint16_t* type, nsIID* iid, uint32_t* count, + void** ptr) { + return mData.ConvertToArray(type, iid, count, ptr); +} + +NS_IMETHODIMP XPCVariant::GetAsStringWithSize(uint32_t* size, char** str) { + return mData.ConvertToStringWithSize(size, str); +} + +NS_IMETHODIMP XPCVariant::GetAsWStringWithSize(uint32_t* size, char16_t** str) { + return mData.ConvertToWStringWithSize(size, str); +} diff --git a/js/xpconnect/src/XPCWrappedJS.cpp b/js/xpconnect/src/XPCWrappedJS.cpp new file mode 100644 index 0000000000..d01c801600 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJS.cpp @@ -0,0 +1,686 @@ +/* -*- 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/. */ + +/* Class that wraps JS objects to appear as XPCOM objects. */ + +#include "xpcprivate.h" +#include "XPCMaps.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/Sprintf.h" +#include "js/Object.h" // JS::GetCompartment +#include "js/RealmIterators.h" +#include "nsCCUncollectableMarker.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +// NOTE: much of the fancy footwork is done in xpcstubs.cpp + +// nsXPCWrappedJS lifetime. +// +// An nsXPCWrappedJS is either rooting its JS object or is subject to +// finalization. The subject-to-finalization state lets wrappers support +// nsSupportsWeakReference in the case where the underlying JS object +// is strongly owned, but the wrapper itself is only weakly owned. +// +// A wrapper is rooting its JS object whenever its refcount is greater than 1. +// In this state, root wrappers are always added to the cycle collector graph. +// The wrapper keeps around an extra refcount, added in the constructor, to +// support the possibility of an eventual transition to the +// subject-to-finalization state. This extra refcount is ignored by the cycle +// collector, which traverses the "self" edge for this refcount. +// +// When the refcount of a rooting wrapper drops to 1, if there is no weak +// reference to the wrapper (which can only happen for the root wrapper), it is +// immediately Destroy()'d. Otherwise, it becomes subject to finalization. +// +// When a wrapper is subject to finalization, the wrapper has a refcount of 1. +// It is now owned exclusively by its JS object. Either a weak reference will be +// turned into a strong ref which will bring its refcount up to 2 and change the +// wrapper back to the rooting state, or it will stay alive until the JS object +// dies. If the JS object dies, then when +// JSObject2WrappedJSMap::UpdateWeakPointersAfterGC is called (via the JS +// engine's weak pointer zone or compartment callbacks) it will find the wrapper +// and call Release() on it, destroying the wrapper. Otherwise, the wrapper will +// stay alive, even if it no longer has a weak reference to it. +// +// When the wrapper is subject to finalization, it is kept alive by an implicit +// reference from the JS object which is invisible to the cycle collector, so +// the cycle collector does not traverse any children of wrappers that are +// subject to finalization. This will result in a leak if a wrapper in the +// non-rooting state has an aggregated native that keeps alive the wrapper's JS +// object. See bug 947049. + +// If traversing wrappedJS wouldn't release it, nor cause any other objects to +// be added to the graph, there is no need to add it to the graph at all. +bool nsXPCWrappedJS::CanSkip() { + if (!nsCCUncollectableMarker::sGeneration) { + return false; + } + + // If this wrapper holds a gray object, need to trace it. + // We can't skip it even if it is subject to finalization, because we want to + // be able to collect it if the JS object is gray. + JSObject* obj = GetJSObjectPreserveColor(); + if (obj && JS::ObjectIsMarkedGray(obj)) { + return false; + } + + // For non-root wrappers, check if the root wrapper will be + // added to the CC graph. + if (!IsRootWrapper()) { + // mRoot points to null after unlinking. + NS_ENSURE_TRUE(mRoot, false); + return mRoot->CanSkip(); + } + + // At this point, the WJS must be a root wrapper with a black JS object, so + // if it is subject to finalization, the JS object will be holding it alive + // so it will be okay to skip it. + + // For the root wrapper, check if there is an aggregated + // native object that will be added to the CC graph. + if (!IsAggregatedToNative()) { + return true; + } + + nsISupports* agg = GetAggregatedNativeObject(); + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(agg, &cp); + nsISupports* canonical = nullptr; + agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast<void**>(&canonical)); + return cp && canonical && cp->CanSkipThis(canonical); +} + +NS_IMETHODIMP +NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::TraverseNative( + void* p, nsCycleCollectionTraversalCallback& cb) { + nsISupports* s = static_cast<nsISupports*>(p); + MOZ_ASSERT(CheckForRightISupports(s), + "not the nsISupports pointer we expect"); + nsXPCWrappedJS* tmp = Downcast(s); + + nsrefcnt refcnt = tmp->mRefCnt.get(); + if (cb.WantDebugInfo()) { + char name[72]; + SprintfLiteral(name, "nsXPCWrappedJS (%s)", tmp->mInfo->Name()); + cb.DescribeRefCountedNode(refcnt, name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsXPCWrappedJS, refcnt) + } + + if (tmp->IsSubjectToFinalization()) { + // If the WJS is subject to finalization, then it can be held alive by its + // JS object. We represent this edge by using NoteWeakMapping. The linked + // list of subject-to-finalization WJS acts like a known-black weak map. + cb.NoteWeakMapping(tmp->GetJSObjectPreserveColor(), s, + NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS)); + } + + // Don't let the extra reference for nsSupportsWeakReference keep a WJS alive. + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self"); + cb.NoteXPCOMChild(s); + + if (tmp->IsRootWrapper()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "aggregated native"); + cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject()); + } else { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root"); + cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper())); + } + + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(nsXPCWrappedJS) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPCWrappedJS) + tmp->Unlink(); + // Note: Unlink already calls ClearWeakReferences, so no need for + // NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE here. +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXPCWrappedJS) + // See the comment at the top of this file for the explanation of + // the weird tracing condition. + if (!tmp->IsSubjectToFinalization()) { + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSObj) + } +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +// WJS are JS holders, so we'll always add them as roots in CCs and we can +// remove them from the purple buffer in between CCs. +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXPCWrappedJS) + return true; +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +nsXPCWrappedJS* nsIXPConnectWrappedJS::AsXPCWrappedJS() { + return static_cast<nsXPCWrappedJS*>(this); +} + +nsresult nsIXPConnectWrappedJS::AggregatedQueryInterface(REFNSIID aIID, + void** aInstancePtr) { + MOZ_ASSERT(AsXPCWrappedJS()->IsAggregatedToNative(), + "bad AggregatedQueryInterface call"); + *aInstancePtr = nullptr; + + if (!AsXPCWrappedJS()->IsValid()) { + return NS_ERROR_UNEXPECTED; + } + + // Put this here rather that in DelegatedQueryInterface because it needs + // to be in QueryInterface before the possible delegation to 'outer', but + // we don't want to do this check twice in one call in the normal case: + // once in QueryInterface and once in DelegatedQueryInterface. + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*)this; + return NS_OK; + } + + return AsXPCWrappedJS()->DelegatedQueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr) { + if (nullptr == aInstancePtr) { + MOZ_ASSERT(false, "null pointer"); + return NS_ERROR_NULL_POINTER; + } + + *aInstancePtr = nullptr; + + if (aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports))) { + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + return NS_OK; + } + + if (!IsValid()) { + return NS_ERROR_UNEXPECTED; + } + + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJSUnmarkGray))) { + *aInstancePtr = nullptr; + + mJSObj.exposeToActiveJS(); + + // Just return some error value since one isn't supposed to use + // nsIXPConnectWrappedJSUnmarkGray objects for anything. + return NS_ERROR_FAILURE; + } + + // Always check for this first so that our 'outer' can get this interface + // from us without recurring into a call to the outer's QI! + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*)static_cast<nsIXPConnectWrappedJS*>(this); + return NS_OK; + } + + nsISupports* outer = GetAggregatedNativeObject(); + if (outer) { + return outer->QueryInterface(aIID, aInstancePtr); + } + + // else... + + return DelegatedQueryInterface(aIID, aInstancePtr); +} + +// For a description of nsXPCWrappedJS lifetime and reference counting, see +// the comment at the top of this file. + +MozExternalRefCountType nsXPCWrappedJS::AddRef(void) { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::AddRef called off main thread"); + + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsISupports* base = + NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.incr(base); + NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this)); + + if (2 == cnt && IsValid()) { + GetJSObject(); // Unmark gray JSObject. + + // This WJS is no longer subject to finalization. + if (isInList()) { + remove(); + } + } + + return cnt; +} + +MozExternalRefCountType nsXPCWrappedJS::Release(void) { + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::Release called off main thread"); + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(nsXPCWrappedJS); + + bool shouldDelete = false; + nsISupports* base = + NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.decr(base, &shouldDelete); + NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS"); + + if (0 == cnt) { + if (MOZ_UNLIKELY(shouldDelete)) { + mRefCnt.stabilizeForDeletion(); + DeleteCycleCollectable(); + } else { + mRefCnt.incr(base); + Destroy(); + mRefCnt.decr(base); + } + } else if (1 == cnt) { + // If we are not a root wrapper being used from a weak reference, + // then the extra ref is not needed and we can let ourselves be + // deleted. + if (!HasWeakReferences()) { + return Release(); + } + + if (IsValid()) { + XPCJSRuntime::Get()->AddSubjectToFinalizationWJS(this); + } + + MOZ_ASSERT(IsRootWrapper(), + "Only root wrappers should have weak references"); + } + return cnt; +} + +NS_IMETHODIMP_(void) +nsXPCWrappedJS::DeleteCycleCollectable(void) { delete this; } + +NS_IMETHODIMP +nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr) { + if (!IsRootWrapper()) { + return mRoot->GetWeakReference(aInstancePtr); + } + + return nsSupportsWeakReference::GetWeakReference(aInstancePtr); +} + +JSObject* nsXPCWrappedJS::GetJSObject() { return mJSObj; } + +JSObject* nsIXPConnectWrappedJS::GetJSObjectGlobal() { + JSObject* obj = AsXPCWrappedJS()->mJSObj; + if (js::IsCrossCompartmentWrapper(obj)) { + JS::Compartment* comp = JS::GetCompartment(obj); + return js::GetFirstGlobalInCompartment(comp); + } + return JS::GetNonCCWObjectGlobal(obj); +} + +// static +nsresult nsXPCWrappedJS::GetNewOrUsed(JSContext* cx, JS::HandleObject jsObj, + REFNSIID aIID, + nsXPCWrappedJS** wrapperResult) { + // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::GetNewOrUsed called off main thread"); + + MOZ_RELEASE_ASSERT(js::GetContextCompartment(cx) == + JS::GetCompartment(jsObj)); + + const nsXPTInterfaceInfo* info = GetInterfaceInfo(aIID); + if (!info) { + return NS_ERROR_FAILURE; + } + + JS::RootedObject rootJSObj(cx, GetRootJSObject(cx, jsObj)); + if (!rootJSObj) { + return NS_ERROR_FAILURE; + } + + xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj); + MOZ_ASSERT(rootComp); + + // Find any existing wrapper. + RefPtr<nsXPCWrappedJS> root = rootComp->GetWrappedJSMap()->Find(rootJSObj); + MOZ_ASSERT_IF(root, !nsXPConnect::GetRuntimeInstance() + ->GetMultiCompartmentWrappedJSMap() + ->Find(rootJSObj)); + if (!root) { + root = nsXPConnect::GetRuntimeInstance() + ->GetMultiCompartmentWrappedJSMap() + ->Find(rootJSObj); + } + + nsresult rv = NS_ERROR_FAILURE; + if (root) { + RefPtr<nsXPCWrappedJS> wrapper = root->FindOrFindInherited(aIID); + if (wrapper) { + wrapper.forget(wrapperResult); + return NS_OK; + } + } else if (rootJSObj != jsObj) { + // Make a new root wrapper, because there is no existing + // root wrapper, and the wrapper we are trying to make isn't + // a root. + const nsXPTInterfaceInfo* rootInfo = + GetInterfaceInfo(NS_GET_IID(nsISupports)); + if (!rootInfo) { + return NS_ERROR_FAILURE; + } + + root = new nsXPCWrappedJS(cx, rootJSObj, rootInfo, nullptr, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr<nsXPCWrappedJS> wrapper = + new nsXPCWrappedJS(cx, jsObj, info, root, &rv); + if (NS_FAILED(rv)) { + return rv; + } + wrapper.forget(wrapperResult); + return NS_OK; +} + +nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, + const nsXPTInterfaceInfo* aInfo, + nsXPCWrappedJS* root, nsresult* rv) + : mJSObj(aJSObj), mInfo(aInfo), mRoot(root ? root : this), mNext(nullptr) { + *rv = InitStub(mInfo->IID()); + // Continue even in the failure case, so that our refcounting/Destroy + // behavior works correctly. + + // There is an extra AddRef to support weak references to wrappers + // that are subject to finalization. See the top of the file for more + // details. + NS_ADDREF_THIS(); + + if (IsRootWrapper()) { + MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here"); + if (!xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx, + this)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } else { + NS_ADDREF(mRoot); + mNext = mRoot->mNext; + mRoot->mNext = this; + + // We always start wrappers in the per-compartment table. If adding + // this wrapper to the chain causes it to cross compartments, we need + // to migrate the chain to the global table on the XPCJSContext. + if (mRoot->IsMultiCompartment()) { + xpc::CompartmentPrivate::Get(mRoot->mJSObj) + ->GetWrappedJSMap() + ->Remove(mRoot); + auto destMap = + nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap(); + if (!destMap->Add(cx, mRoot)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } + } + + mozilla::HoldJSObjects(this); +} + +nsXPCWrappedJS::~nsXPCWrappedJS() { Destroy(); } + +void XPCJSRuntime::RemoveWrappedJS(nsXPCWrappedJS* wrapper) { + AssertInvalidWrappedJSNotInTable(wrapper); + if (!wrapper->IsValid()) { + return; + } + + // It is possible for the same JS XPCOM implementation object to be wrapped + // with a different interface in multiple JS::Compartments. In this case, the + // wrapper chain will contain references to multiple compartments. While we + // always store single-compartment chains in the per-compartment wrapped-js + // table, chains in the multi-compartment wrapped-js table may contain + // single-compartment chains, if they have ever contained a wrapper in a + // different compartment. Since removal requires a lookup anyway, we just do + // the remove on both tables unconditionally. + MOZ_ASSERT_IF( + wrapper->IsMultiCompartment(), + !xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor()) + ->GetWrappedJSMap() + ->HasWrapper(wrapper)); + GetMultiCompartmentWrappedJSMap()->Remove(wrapper); + xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor()) + ->GetWrappedJSMap() + ->Remove(wrapper); +} + +#ifdef DEBUG +static JS::CompartmentIterResult NotHasWrapperAssertionCallback( + JSContext* cx, void* data, JS::Compartment* comp) { + auto wrapper = static_cast<nsXPCWrappedJS*>(data); + auto xpcComp = xpc::CompartmentPrivate::Get(comp); + MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper)); + return JS::CompartmentIterResult::KeepGoing; +} +#endif + +void XPCJSRuntime::AssertInvalidWrappedJSNotInTable( + nsXPCWrappedJS* wrapper) const { +#ifdef DEBUG + if (!wrapper->IsValid()) { + MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper)); + if (!mGCIsRunning) { + JSContext* cx = XPCJSContext::Get()->Context(); + JS_IterateCompartments(cx, wrapper, NotHasWrapperAssertionCallback); + } + } +#endif +} + +void nsXPCWrappedJS::Destroy() { + MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion"); + + if (IsRootWrapper()) { + nsXPConnect::GetRuntimeInstance()->RemoveWrappedJS(this); + } + Unlink(); +} + +void nsXPCWrappedJS::Unlink() { + nsXPConnect::GetRuntimeInstance()->AssertInvalidWrappedJSNotInTable(this); + + if (IsValid()) { + XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance(); + if (rt) { + if (IsRootWrapper()) { + rt->RemoveWrappedJS(this); + } + } + + mJSObj = nullptr; + } + + if (IsRootWrapper()) { + if (isInList()) { + remove(); + } + ClearWeakReferences(); + } else if (mRoot) { + // unlink this wrapper + nsXPCWrappedJS* cur = mRoot; + while (1) { + if (cur->mNext == this) { + cur->mNext = mNext; + break; + } + cur = cur->mNext; + MOZ_ASSERT(cur, "failed to find wrapper in its own chain"); + } + + // Note: unlinking this wrapper may have changed us from a multi- + // compartment wrapper chain to a single-compartment wrapper chain. We + // leave the wrapper in the multi-compartment table as it is likely to + // need to be multi-compartment again in the future and, moreover, we + // cannot get a JSContext here. + + // let the root go + NS_RELEASE(mRoot); + } + + if (mOuter) { + XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance(); + if (rt->GCIsRunning()) { + DeferredFinalize(mOuter.forget().take()); + } else { + mOuter = nullptr; + } + } + + mozilla::DropJSObjects(this); +} + +bool nsXPCWrappedJS::IsMultiCompartment() const { + MOZ_ASSERT(IsRootWrapper()); + JS::Compartment* compartment = Compartment(); + nsXPCWrappedJS* next = mNext; + while (next) { + if (next->Compartment() != compartment) { + return true; + } + next = next->mNext; + } + return false; +} + +nsXPCWrappedJS* nsXPCWrappedJS::Find(REFNSIID aIID) { + if (aIID.Equals(NS_GET_IID(nsISupports))) { + return mRoot; + } + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + if (aIID.Equals(cur->GetIID())) { + return cur; + } + } + + return nullptr; +} + +// check if asking for an interface that some wrapper in the chain inherits from +nsXPCWrappedJS* nsXPCWrappedJS::FindInherited(REFNSIID aIID) { + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports)), "bad call sequence"); + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + if (cur->mInfo->HasAncestor(aIID)) { + return cur; + } + } + + return nullptr; +} + +nsresult nsIXPConnectWrappedJS::GetInterfaceIID(nsIID** iid) { + MOZ_ASSERT(iid, "bad param"); + + *iid = AsXPCWrappedJS()->GetIID().Clone(); + return NS_OK; +} + +void nsXPCWrappedJS::SystemIsBeingShutDown() { + // XXX It turns out that it is better to leak here then to do any Releases + // and have them propagate into all sorts of mischief as the system is being + // shutdown. This was learned the hard way :( + + // mJSObj == nullptr is used to indicate that the wrapper is no longer valid + // and that calls should fail without trying to use any of the + // xpconnect mechanisms. 'IsValid' is implemented by checking this pointer. + + // Clear the contents of the pointer using unsafeGet() to avoid + // triggering post barriers in shutdown, as this will access the chunk + // containing mJSObj, which may have been freed at this point. This is safe + // if we are not currently running an incremental GC. + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(xpc_GetSafeJSContext())); + *mJSObj.unsafeGet() = nullptr; + if (isInList()) { + remove(); + } + + // Notify other wrappers in the chain. + if (mNext) { + mNext->SystemIsBeingShutDown(); + } +} + +size_t nsXPCWrappedJS::SizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + // mJSObject is a JS pointer, so don't measure the object. mInfo is + // not dynamically allocated. mRoot is not measured because it is + // either |this| or we have already measured it. mOuter is rare and + // probably not uniquely owned by this. + size_t n = mallocSizeOf(this); + n += nsAutoXPTCStub::SizeOfExcludingThis(mallocSizeOf); + + // Wrappers form a linked list via the mNext field, so include them all + // in the measurement. Only root wrappers are stored in the map, so + // everything will be measured exactly once. + if (mNext) { + n += mNext->SizeOfIncludingThis(mallocSizeOf); + } + + return n; +} + +/***************************************************************************/ + +nsresult nsIXPConnectWrappedJS::DebugDump(int16_t depth) { + return AsXPCWrappedJS()->DebugDump(depth); +} + +nsresult nsXPCWrappedJS::DebugDump(int16_t depth) { +#ifdef DEBUG + XPC_LOG_ALWAYS( + ("nsXPCWrappedJS @ %p with mRefCnt = %" PRIuPTR, this, mRefCnt.get())); + XPC_LOG_INDENT(); + + XPC_LOG_ALWAYS(("%s wrapper around JSObject @ %p", + IsRootWrapper() ? "ROOT" : "non-root", mJSObj.get())); + const char* name = mInfo->Name(); + XPC_LOG_ALWAYS(("interface name is %s", name)); + auto iid = mInfo->IID().ToString(); + XPC_LOG_ALWAYS(("IID number is %s", iid.get())); + XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p", mInfo)); + + if (!IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } + if (mNext) { + if (IsRootWrapper()) { + XPC_LOG_ALWAYS(("Additional wrappers for this object...")); + XPC_LOG_INDENT(); + } + mNext->DebugDump(depth); + if (IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } + } + if (IsRootWrapper()) { + XPC_LOG_OUTDENT(); + } +#endif + return NS_OK; +} diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp new file mode 100644 index 0000000000..0c1627fdc4 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJSClass.cpp @@ -0,0 +1,1094 @@ +/* -*- 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/. */ + +/* Sharable code and data for wrapper around JSObjects. */ + +#include "xpcprivate.h" +#include "js/CallAndConstruct.h" // JS_CallFunctionValue +#include "js/Object.h" // JS::GetClass +#include "js/Printf.h" +#include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_HasPropertyById, JS_SetProperty, JS_SetPropertyById +#include "nsArrayEnumerator.h" +#include "nsINamed.h" +#include "nsIScriptError.h" +#include "nsWrapperCache.h" +#include "AccessCheck.h" +#include "nsJSUtils.h" +#include "nsPrintfCString.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/MozQueryInterface.h" + +#include "jsapi.h" +#include "jsfriendapi.h" + +using namespace xpc; +using namespace JS; +using namespace mozilla; +using namespace mozilla::dom; + +bool AutoScriptEvaluate::StartEvaluating(HandleObject scope) { + MOZ_ASSERT(!mEvaluated, + "AutoScriptEvaluate::Evaluate should only be called once"); + + if (!mJSContext) { + return true; + } + + mEvaluated = true; + + mAutoRealm.emplace(mJSContext, scope); + + // Saving the exception state keeps us from interfering with another script + // that may also be running on this context. This occurred first with the + // js debugger, as described in + // http://bugzilla.mozilla.org/show_bug.cgi?id=88130 but presumably could + // show up in any situation where a script calls into a wrapped js component + // on the same context, while the context has a nonzero exception state. + mState.emplace(mJSContext); + + return true; +} + +AutoScriptEvaluate::~AutoScriptEvaluate() { + if (!mJSContext || !mEvaluated) { + return; + } + mState->restore(); +} + +// It turns out that some errors may be not worth reporting. So, this +// function is factored out to manage that. +bool xpc_IsReportableErrorCode(nsresult code) { + if (NS_SUCCEEDED(code)) { + return false; + } + + switch (code) { + // Error codes that we don't want to report as errors... + // These generally indicate bad interface design AFAIC. + case NS_ERROR_FACTORY_REGISTER_AGAIN: + case NS_BASE_STREAM_WOULD_BLOCK: + return false; + default: + return true; + } +} + +// A little stack-based RAII class to help management of the XPCJSContext +// PendingResult. +class MOZ_STACK_CLASS AutoSavePendingResult { + public: + explicit AutoSavePendingResult(XPCJSContext* xpccx) : mXPCContext(xpccx) { + // Save any existing pending result and reset to NS_OK for this invocation. + mSavedResult = xpccx->GetPendingResult(); + xpccx->SetPendingResult(NS_OK); + } + ~AutoSavePendingResult() { mXPCContext->SetPendingResult(mSavedResult); } + + private: + XPCJSContext* mXPCContext; + nsresult mSavedResult; +}; + +// static +const nsXPTInterfaceInfo* nsXPCWrappedJS::GetInterfaceInfo(REFNSIID aIID) { + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID); + if (!info || info->IsBuiltinClass()) { + return nullptr; + } + + return info; +} + +// static +JSObject* nsXPCWrappedJS::CallQueryInterfaceOnJSObject(JSContext* cx, + JSObject* jsobjArg, + HandleObject scope, + REFNSIID aIID) { + js::AssertSameCompartment(scope, jsobjArg); + + RootedObject jsobj(cx, jsobjArg); + RootedValue arg(cx); + RootedValue retval(cx); + RootedObject retObj(cx); + RootedValue fun(cx); + + // In bug 503926, we added a security check to make sure that we don't + // invoke content QI functions. In the modern world, this is probably + // unnecessary, because invoking QI involves passing an IID object to + // content, which will fail. But we do a belt-and-suspenders check to + // make sure that content can never trigger the rat's nest of code below. + // Once we completely turn off XPConnect for the web, this can definitely + // go away. + if (!AccessCheck::isChrome(jsobj) || + !AccessCheck::isChrome(js::UncheckedUnwrap(jsobj))) { + return nullptr; + } + + // OK, it looks like we'll be calling into JS code. + AutoScriptEvaluate scriptEval(cx); + + // XXX we should install an error reporter that will send reports to + // the JS error console service. + if (!scriptEval.StartEvaluating(scope)) { + return nullptr; + } + + // check upfront for the existence of the function property + HandleId funid = + XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE); + if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || fun.isPrimitive()) { + return nullptr; + } + + dom::MozQueryInterface* mozQI = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(MozQueryInterface, &fun, mozQI))) { + if (mozQI->QueriesTo(aIID)) { + return jsobj.get(); + } + return nullptr; + } + + if (!xpc::ID2JSValue(cx, aIID, &arg)) { + return nullptr; + } + + // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It is + // not an exception that is ever worth reporting, but we don't want to eat + // all exceptions either. + + bool success = + JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval); + if (!success && JS_IsExceptionPending(cx)) { + RootedValue jsexception(cx, NullValue()); + + if (JS_GetPendingException(cx, &jsexception)) { + if (jsexception.isObject()) { + // XPConnect may have constructed an object to represent a + // C++ QI failure. See if that is the case. + JS::Rooted<JSObject*> exceptionObj(cx, &jsexception.toObject()); + Exception* e = nullptr; + UNWRAP_OBJECT(Exception, &exceptionObj, e); + + if (e && e->GetResult() == NS_NOINTERFACE) { + JS_ClearPendingException(cx); + } + } else if (jsexception.isNumber()) { + nsresult rv; + // JS often throws an nsresult. + if (jsexception.isDouble()) + // Visual Studio 9 doesn't allow casting directly from + // a double to an enumeration type, contrary to + // 5.2.9(10) of C++11, so add an intermediate cast. + rv = (nsresult)(uint32_t)(jsexception.toDouble()); + else + rv = (nsresult)(jsexception.toInt32()); + + if (rv == NS_NOINTERFACE) JS_ClearPendingException(cx); + } + } + } else if (!success) { + NS_WARNING("QI hook ran OOMed - this is probably a bug!"); + } + + if (success) success = JS_ValueToObject(cx, retval, &retObj); + + return success ? retObj.get() : nullptr; +} + +/***************************************************************************/ + +namespace { + +class WrappedJSNamed final : public nsINamed { + nsCString mName; + + ~WrappedJSNamed() = default; + + public: + NS_DECL_ISUPPORTS + + explicit WrappedJSNamed(const nsACString& aName) : mName(aName) {} + + NS_IMETHOD GetName(nsACString& aName) override { + aName = mName; + aName.AppendLiteral(":JS"); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(WrappedJSNamed, nsINamed) + +} // anonymous namespace + +/***************************************************************************/ + +// static +nsresult nsXPCWrappedJS::DelegatedQueryInterface(REFNSIID aIID, + void** aInstancePtr) { + if (aIID.Equals(NS_GET_IID(nsIXPConnectJSObjectHolder))) { + // This needs to call NS_ADDREF directly instead of using nsCOMPtr<>, + // because the latter does a QI in an assert, and we're already in a QI, so + // it would cause infinite recursion. + NS_ADDREF(this); + *aInstancePtr = (void*)static_cast<nsIXPConnectJSObjectHolder*>(this); + return NS_OK; + } + + // Ensure that we are asking for a non-builtinclass interface, and avoid even + // setting up our AutoEntryScript if we are. Don't bother doing that check + // if our IID is nsISupports: we know that's not builtinclass, and we QI to + // it a _lot_. + if (!aIID.Equals(NS_GET_IID(nsISupports))) { + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID); + if (!info || info->IsBuiltinClass()) { + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupportsWeakReference)), + "Later code for nsISupportsWeakReference is being skipped"); + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISimpleEnumerator)), + "Later code for nsISimpleEnumerator is being skipped"); + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsINamed)), + "Later code for nsINamed is being skipped"); + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + } + + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsWrapperCache)), + "Where did we get non-builtinclass interface info for this??"); + + // QI on an XPCWrappedJS can run script, so we need an AutoEntryScript. + // This is inherently Gecko-specific. + // We check both nativeGlobal and nativeGlobal->GetGlobalJSObject() even + // though we have derived nativeGlobal from the JS global, because we know + // there are cases where this can happen. See bug 1094953. + RootedObject obj(RootingCx(), GetJSObject()); + nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj)); + NS_ENSURE_TRUE(nativeGlobal, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(nativeGlobal->HasJSGlobal(), NS_ERROR_FAILURE); + + AutoAllowLegacyScriptExecution exemption; + + AutoEntryScript aes(nativeGlobal, "XPCWrappedJS QueryInterface", + /* aIsMainThread = */ true); + XPCCallContext ccx(aes.cx()); + if (!ccx.IsValid()) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + // We now need to enter the realm of the actual JSObject* we are pointing at. + // But that may be a cross-compartment wrapper and therefore not have a + // well-defined realm, so enter the realm of the global that we grabbed back + // when we started pointing to our JSObject*. + RootedObject objScope(RootingCx(), GetJSObjectGlobal()); + JSAutoRealm ar(aes.cx(), objScope); + + // We support nsISupportsWeakReference iff the root wrapped JSObject + // claims to support it in its QueryInterface implementation. + if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) { + // We only want to expose one implementation from our aggregate. + nsXPCWrappedJS* root = GetRootWrapper(); + RootedObject rootScope(ccx, root->GetJSObjectGlobal()); + + // Fail if JSObject doesn't claim support for nsISupportsWeakReference + if (!root->IsValid() || !CallQueryInterfaceOnJSObject( + ccx, root->GetJSObject(), rootScope, aIID)) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + NS_ADDREF(root); + *aInstancePtr = (void*)static_cast<nsISupportsWeakReference*>(root); + return NS_OK; + } + + // If we're asked to QI to nsISimpleEnumerator and the wrapped object does not + // have a QueryInterface method, assume it is a JS iterator, and wrap it into + // an equivalent nsISimpleEnumerator. + if (aIID.Equals(NS_GET_IID(nsISimpleEnumerator))) { + bool found; + XPCJSContext* xpccx = ccx.GetContext(); + if (JS_HasPropertyById(aes.cx(), obj, + xpccx->GetStringID(xpccx->IDX_QUERY_INTERFACE), + &found) && + !found) { + nsresult rv; + nsCOMPtr<nsIJSEnumerator> jsEnum; + if (!XPCConvert::JSObject2NativeInterface( + aes.cx(), getter_AddRefs(jsEnum), obj, + &NS_GET_IID(nsIJSEnumerator), nullptr, &rv)) { + return rv; + } + nsCOMPtr<nsISimpleEnumerator> res = new XPCWrappedJSIterator(jsEnum); + res.forget(aInstancePtr); + return NS_OK; + } + } + + // Checks for any existing wrapper explicitly constructed for this iid. + // This includes the current wrapper. This also deals with the + // nsISupports case (for which it returns mRoot). + // Also check if asking for an interface from which one of our wrappers + // inherits. + if (nsXPCWrappedJS* sibling = FindOrFindInherited(aIID)) { + NS_ADDREF(sibling); + *aInstancePtr = sibling->GetXPTCStub(); + return NS_OK; + } + + // Check if the desired interface is a function interface. If so, we don't + // want to QI, because the function almost certainly doesn't have a + // QueryInterface property, and doesn't need one. + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(aIID); + if (info && info->IsFunction()) { + RefPtr<nsXPCWrappedJS> wrapper; + nsresult rv = + nsXPCWrappedJS::GetNewOrUsed(ccx, obj, aIID, getter_AddRefs(wrapper)); + + // Do the same thing we do for the "check for any existing wrapper" case + // above. + if (NS_SUCCEEDED(rv) && wrapper) { + *aInstancePtr = wrapper.forget().take()->GetXPTCStub(); + } + return rv; + } + + // else we do the more expensive stuff... + + // check if the JSObject claims to implement this interface + RootedObject jsobj(ccx, + CallQueryInterfaceOnJSObject(ccx, obj, objScope, aIID)); + if (jsobj) { + // We can't use XPConvert::JSObject2NativeInterface() here + // since that can find a XPCWrappedNative directly on the + // proto chain, and we don't want that here. We need to find + // the actual JS object that claimed it supports the interface + // we're looking for or we'll potentially bypass security + // checks etc by calling directly through to a native found on + // the prototype chain. + // + // Instead, simply do the nsXPCWrappedJS part of + // XPConvert::JSObject2NativeInterface() here to make sure we + // get a new (or used) nsXPCWrappedJS. + RefPtr<nsXPCWrappedJS> wrapper; + nsresult rv = + nsXPCWrappedJS::GetNewOrUsed(ccx, jsobj, aIID, getter_AddRefs(wrapper)); + if (NS_SUCCEEDED(rv) && wrapper) { + // We need to go through the QueryInterface logic to make + // this return the right thing for the various 'special' + // interfaces; e.g. nsISimpleEnumerator. + rv = wrapper->QueryInterface(aIID, aInstancePtr); + return rv; + } + } + + // If we're asked to QI to nsINamed, we pretend that this is possible. We'll + // try to return a name that makes sense for the wrapped JS value. + if (aIID.Equals(NS_GET_IID(nsINamed))) { + nsCString name = GetFunctionName(ccx, obj); + RefPtr<WrappedJSNamed> named = new WrappedJSNamed(name); + *aInstancePtr = named.forget().take(); + return NS_OK; + } + + // else... + // no can do + *aInstancePtr = nullptr; + return NS_NOINTERFACE; +} + +// static +JSObject* nsXPCWrappedJS::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg) { + RootedObject aJSObj(cx, aJSObjArg); + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + JSObject* result = + CallQueryInterfaceOnJSObject(cx, aJSObj, global, NS_GET_IID(nsISupports)); + if (!result) { + result = aJSObj; + } + return js::UncheckedUnwrap(result); +} + +// static +bool nsXPCWrappedJS::GetArraySizeFromParam(const nsXPTMethodInfo* method, + const nsXPTType& type, + nsXPTCMiniVariant* nativeParams, + uint32_t* result) { + if (type.Tag() != nsXPTType::T_LEGACY_ARRAY && + type.Tag() != nsXPTType::T_PSTRING_SIZE_IS && + type.Tag() != nsXPTType::T_PWSTRING_SIZE_IS) { + *result = 0; + return true; + } + + uint8_t argnum = type.ArgNum(); + const nsXPTParamInfo& param = method->Param(argnum); + + // This should be enforced by the xpidl compiler, but it's not. + // See bug 695235. + if (param.Type().Tag() != nsXPTType::T_U32) { + return false; + } + + // If the length is passed indirectly (as an outparam), dereference by an + // extra level. + if (param.IsIndirect()) { + *result = *(uint32_t*)nativeParams[argnum].val.p; + } else { + *result = nativeParams[argnum].val.u32; + } + return true; +} + +// static +bool nsXPCWrappedJS::GetInterfaceTypeFromParam(const nsXPTMethodInfo* method, + const nsXPTType& type, + nsXPTCMiniVariant* nativeParams, + nsID* result) { + result->Clear(); + + const nsXPTType& inner = type.InnermostType(); + if (inner.Tag() == nsXPTType::T_INTERFACE) { + // Directly get IID from nsXPTInterfaceInfo. + if (!inner.GetInterface()) { + return false; + } + + *result = inner.GetInterface()->IID(); + } else if (inner.Tag() == nsXPTType::T_INTERFACE_IS) { + // Get IID from a passed parameter. + const nsXPTParamInfo& param = method->Param(inner.ArgNum()); + if (param.Type().Tag() != nsXPTType::T_NSID && + param.Type().Tag() != nsXPTType::T_NSIDPTR) { + return false; + } + + void* ptr = nativeParams[inner.ArgNum()].val.p; + + // If our IID is passed as a pointer outparameter, an extra level of + // dereferencing is required. + if (ptr && param.Type().Tag() == nsXPTType::T_NSIDPTR && + param.IsIndirect()) { + ptr = *(nsID**)ptr; + } + + if (!ptr) { + return false; + } + + *result = *(nsID*)ptr; + } + return true; +} + +// static +void nsXPCWrappedJS::CleanupOutparams(const nsXPTMethodInfo* info, + nsXPTCMiniVariant* nativeParams, + bool inOutOnly, uint8_t count) { + // clean up any 'out' params handed in + for (uint8_t i = 0; i < count; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + if (!param.IsOut()) { + continue; + } + + MOZ_ASSERT(param.IsIndirect(), "Outparams are always indirect"); + + // Don't try to clear optional out params that are not set. + if (param.IsOptional() && !nativeParams[i].val.p) { + continue; + } + + // Call 'CleanupValue' on parameters which we know to be initialized: + // 1. Complex parameters (initialized by caller) + // 2. 'inout' parameters (initialized by caller) + // 3. 'out' parameters when 'inOutOnly' is 'false' (initialized by us) + // + // We skip non-complex 'out' parameters before the call, as they may + // contain random junk. + if (param.Type().IsComplex() || param.IsIn() || !inOutOnly) { + uint32_t arrayLen = 0; + if (!GetArraySizeFromParam(info, param.Type(), nativeParams, &arrayLen)) { + continue; + } + + xpc::CleanupValue(param.Type(), nativeParams[i].val.p, arrayLen); + } + + // Ensure our parameters are in a clean state. Complex values are always + // handled by CleanupValue, and others have a valid null representation. + if (!param.Type().IsComplex()) { + param.Type().ZeroValue(nativeParams[i].val.p); + } + } +} + +nsresult nsXPCWrappedJS::CheckForException(XPCCallContext& ccx, + AutoEntryScript& aes, + HandleObject aObj, + const char* aPropertyName, + const char* anInterfaceName, + Exception* aSyntheticException) { + JSContext* cx = ccx.GetJSContext(); + MOZ_ASSERT(cx == aes.cx()); + RefPtr<Exception> xpc_exception = aSyntheticException; + /* this one would be set by our error reporter */ + + XPCJSContext* xpccx = ccx.GetContext(); + + // Get this right away in case we do something below to cause JS code + // to run. + nsresult pending_result = xpccx->GetPendingResult(); + + RootedValue js_exception(cx); + bool is_js_exception = JS_GetPendingException(cx, &js_exception); + + /* JS might throw an exception whether the reporter was called or not */ + if (is_js_exception) { + if (!xpc_exception) { + XPCConvert::JSValToXPCException(cx, &js_exception, anInterfaceName, + aPropertyName, + getter_AddRefs(xpc_exception)); + } + + /* cleanup and set failed even if we can't build an exception */ + if (!xpc_exception) { + xpccx->SetPendingException(nullptr); // XXX necessary? + } + } + + // Clear the pending exception now, because xpc_exception might be JS- + // implemented, so invoking methods on it might re-enter JS, which we can't + // do with an exception on the stack. + aes.ClearException(); + + if (xpc_exception) { + nsresult e_result = xpc_exception->GetResult(); + // Figure out whether or not we should report this exception. + bool reportable = xpc_IsReportableErrorCode(e_result); + if (reportable) { + // Ugly special case for GetInterface. It's "special" in the + // same way as QueryInterface in that a failure is not + // exceptional and shouldn't be reported. We have to do this + // check here instead of in xpcwrappedjs (like we do for QI) to + // avoid adding extra code to all xpcwrappedjs objects. + if (e_result == NS_ERROR_NO_INTERFACE && + !strcmp(anInterfaceName, "nsIInterfaceRequestor") && + !strcmp(aPropertyName, "getInterface")) { + reportable = false; + } + + // More special case, see bug 877760. + if (e_result == NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) { + reportable = false; + } + } + + // Try to use the error reporter set on the context to handle this + // error if it came from a JS exception. + if (reportable && is_js_exception) { + // Note that we cleared the exception above, so we need to set it again, + // just so that we can tell the JS engine to pass it back to us via the + // error reporting callback. This is all very dumb. + JS_SetPendingException(cx, js_exception); + + // Enter the unwrapped object's realm. This is the realm that was used to + // enter the AutoEntryScript. + JSAutoRealm ar(cx, js::UncheckedUnwrap(aObj)); + aes.ReportException(); + reportable = false; + } + + if (reportable) { + if (nsJSUtils::DumpEnabled()) { + static const char line[] = + "************************************************************\n"; + static const char preamble[] = + "* Call to xpconnect wrapped JSObject produced this error: *\n"; + static const char cant_get_text[] = + "FAILED TO GET TEXT FROM EXCEPTION\n"; + + fputs(line, stdout); + fputs(preamble, stdout); + nsCString text; + xpc_exception->ToString(cx, text); + if (!text.IsEmpty()) { + fputs(text.get(), stdout); + fputs("\n", stdout); + } else + fputs(cant_get_text, stdout); + fputs(line, stdout); + } + + // Log the exception to the JS Console, so that users can do + // something with it. + nsCOMPtr<nsIConsoleService> consoleService( + do_GetService(XPC_CONSOLE_CONTRACTID)); + if (nullptr != consoleService) { + nsCOMPtr<nsIScriptError> scriptError = + do_QueryInterface(xpc_exception->GetData()); + + if (nullptr == scriptError) { + // No luck getting one from the exception, so + // try to cook one up. + scriptError = do_CreateInstance(XPC_SCRIPT_ERROR_CONTRACTID); + if (nullptr != scriptError) { + nsCString newMessage; + xpc_exception->ToString(cx, newMessage); + // try to get filename, lineno from the first + // stack frame location. + int32_t lineNumber = 0; + nsString sourceName; + + nsCOMPtr<nsIStackFrame> location = xpc_exception->GetLocation(); + if (location) { + // Get line number. + lineNumber = location->GetLineNumber(cx); + + // get a filename. + location->GetFilename(cx, sourceName); + } + + nsresult rv = scriptError->InitWithWindowID( + NS_ConvertUTF8toUTF16(newMessage), sourceName, u""_ns, + lineNumber, 0, 0, "XPConnect JavaScript", + nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx)); + if (NS_FAILED(rv)) { + scriptError = nullptr; + } + + rv = scriptError->InitSourceId(location->GetSourceId(cx)); + if (NS_FAILED(rv)) { + scriptError = nullptr; + } + } + } + if (nullptr != scriptError) { + consoleService->LogMessage(scriptError); + } + } + } + // Whether or not it passes the 'reportable' test, it might + // still be an error and we have to do the right thing here... + if (NS_FAILED(e_result)) { + xpccx->SetPendingException(xpc_exception); + return e_result; + } + } else { + // see if JS code signaled failure result without throwing exception + if (NS_FAILED(pending_result)) { + return pending_result; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXPCWrappedJS::CallMethod(uint16_t methodIndex, const nsXPTMethodInfo* info, + nsXPTCMiniVariant* nativeParams) { + // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::CallMethod called off main thread"); + + if (!IsValid()) { + return NS_ERROR_UNEXPECTED; + } + + // We need to reject an attempt to call a non-reflectable method before + // we do anything like AutoEntryScript which might allocate in the JS engine, + // because the method isn't marked with JS_HAZ_CAN_RUN_SCRIPT, and we want + // to be able to take advantage of that in the GC hazard analysis. + if (!info->IsReflectable()) { + return NS_ERROR_FAILURE; + } + + Value* sp = nullptr; + Value* argv = nullptr; + uint8_t i; + nsresult retval = NS_ERROR_FAILURE; + bool success; + bool readyToDoTheCall = false; + nsID param_iid; + bool foundDependentParam; + + // We're about to call into script via an XPCWrappedJS, so we need an + // AutoEntryScript. This is probably Gecko-specific at this point, and + // definitely will be when we turn off XPConnect for the web. + RootedObject obj(RootingCx(), GetJSObject()); + nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj)); + + AutoAllowLegacyScriptExecution exemption; + + AutoEntryScript aes(nativeGlobal, "XPCWrappedJS method call", + /* aIsMainThread = */ true); + XPCCallContext ccx(aes.cx()); + if (!ccx.IsValid()) { + return retval; + } + + JSContext* cx = ccx.GetJSContext(); + + if (!cx) { + return NS_ERROR_FAILURE; + } + + // We now need to enter the realm of the actual JSObject* we are pointing at. + // But that may be a cross-compartment wrapper and therefore not have a + // well-defined realm, so enter the realm of the global that we grabbed back + // when we started pointing to our JSObject*. + RootedObject scope(cx, GetJSObjectGlobal()); + JSAutoRealm ar(cx, scope); + + const nsXPTInterfaceInfo* interfaceInfo = GetInfo(); + JS::RootedId id(cx); + const char* name = info->NameOrDescription(); + if (!info->GetId(cx, id.get())) { + return NS_ERROR_FAILURE; + } + + // [optional_argc] has a different calling convention, which we don't + // support for JS-implemented components. + if (info->WantsOptArgc()) { + const char* str = + "IDL methods marked with [optional_argc] may not " + "be implemented in JS"; + // Throw and warn for good measure. + JS_ReportErrorASCII(cx, "%s", str); + NS_WARNING(str); + return CheckForException(ccx, aes, obj, name, interfaceInfo->Name()); + } + + RootedValue fval(cx); + RootedObject thisObj(cx, obj); + + RootedValueVector args(cx); + AutoScriptEvaluate scriptEval(cx); + + XPCJSRuntime* xpcrt = XPCJSRuntime::Get(); + XPCJSContext* xpccx = ccx.GetContext(); + AutoSavePendingResult apr(xpccx); + + // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. + uint8_t paramCount = info->GetParamCount(); + uint8_t argc = paramCount; + if (info->HasRetval()) { + argc -= 1; + } + + if (!scriptEval.StartEvaluating(scope)) { + goto pre_call_clean_up; + } + + xpccx->SetPendingException(nullptr); + + // We use js_Invoke so that the gcthings we use as args will be rooted by + // the engine as we do conversions and prepare to do the function call. + + // setup stack + + // if this isn't a function call then we don't need to push extra stuff + if (!(info->IsSetter() || info->IsGetter())) { + // We get fval before allocating the stack to avoid gc badness that can + // happen if the GetProperty call leaves our request and the gc runs + // while the stack we allocate contains garbage. + + // If the interface is marked as a [function] then we will assume that + // our JSObject is a function and not an object with a named method. + + // In the xpidl [function] case we are making sure now that the + // JSObject is callable. If it is *not* callable then we silently + // fallback to looking up the named property... + // (because jst says he thinks this fallback is 'The Right Thing'.) + // + // In the normal (non-function) case we just lookup the property by + // name and as long as the object has such a named property we go ahead + // and try to make the call. If it turns out the named property is not + // a callable object then the JS engine will throw an error and we'll + // pass this along to the caller as an exception/result code. + + fval = ObjectValue(*obj); + if (!interfaceInfo->IsFunction() || + JS_TypeOfValue(ccx, fval) != JSTYPE_FUNCTION) { + if (!JS_GetPropertyById(cx, obj, id, &fval)) { + goto pre_call_clean_up; + } + // XXX We really want to factor out the error reporting better and + // specifically report the failure to find a function with this name. + // This is what we do below if the property is found but is not a + // function. We just need to factor better so we can get to that + // reporting path from here. + + thisObj = obj; + } + } + + if (!args.resize(argc)) { + retval = NS_ERROR_OUT_OF_MEMORY; + goto pre_call_clean_up; + } + + argv = args.begin(); + sp = argv; + + // build the args + // NB: This assignment *looks* wrong because we haven't yet called our + // function. However, we *have* already entered the compartmen that we're + // about to call, and that's the global that we want here. In other words: + // we're trusting the JS engine to come up with a good global to use for + // our object (whatever it was). + for (i = 0; i < argc; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + const nsXPTType& type = param.GetType(); + uint32_t array_count; + RootedValue val(cx, NullValue()); + + // Verify that null was not passed for a non-optional 'out' param. + if (param.IsOut() && !nativeParams[i].val.p && !param.IsOptional()) { + retval = NS_ERROR_INVALID_ARG; + goto pre_call_clean_up; + } + + if (param.IsIn()) { + const void* pv; + if (param.IsIndirect()) { + pv = nativeParams[i].val.p; + } else { + pv = &nativeParams[i]; + } + + if (!GetInterfaceTypeFromParam(info, type, nativeParams, ¶m_iid) || + !GetArraySizeFromParam(info, type, nativeParams, &array_count)) + goto pre_call_clean_up; + + if (!XPCConvert::NativeData2JS(cx, &val, pv, type, ¶m_iid, + array_count, nullptr)) + goto pre_call_clean_up; + } + + if (param.IsOut()) { + // create an 'out' object + RootedObject out_obj(cx, NewOutObject(cx)); + if (!out_obj) { + retval = NS_ERROR_OUT_OF_MEMORY; + goto pre_call_clean_up; + } + + if (param.IsIn()) { + if (!JS_SetPropertyById(cx, out_obj, + xpcrt->GetStringID(XPCJSContext::IDX_VALUE), + val)) { + goto pre_call_clean_up; + } + } + *sp++ = JS::ObjectValue(*out_obj); + } else + *sp++ = val; + } + + readyToDoTheCall = true; + +pre_call_clean_up: + // clean up any 'out' params handed in + CleanupOutparams(info, nativeParams, /* inOutOnly = */ true, paramCount); + + if (!readyToDoTheCall) { + return retval; + } + + // do the deed - note exceptions + + MOZ_ASSERT(!aes.HasException()); + + RefPtr<Exception> syntheticException; + RootedValue rval(cx); + if (info->IsGetter()) { + success = JS_GetProperty(cx, obj, name, &rval); + } else if (info->IsSetter()) { + rval = *argv; + success = JS_SetProperty(cx, obj, name, rval); + } else { + if (!fval.isPrimitive()) { + success = JS_CallFunctionValue(cx, thisObj, fval, args, &rval); + } else { + // The property was not an object so can't be a function. + // Let's build and 'throw' an exception. + + static const nsresult code = NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; + static const char format[] = "%s \"%s\""; + const char* msg; + UniqueChars sz; + + if (nsXPCException::NameAndFormatForNSResult(code, nullptr, &msg) && + msg) { + sz = JS_smprintf(format, msg, name); + } + + XPCConvert::ConstructException( + code, sz.get(), interfaceInfo->Name(), name, nullptr, + getter_AddRefs(syntheticException), nullptr, nullptr); + success = false; + } + } + + if (!success) { + return CheckForException(ccx, aes, obj, name, interfaceInfo->Name(), + syntheticException); + } + + xpccx->SetPendingException(nullptr); // XXX necessary? + + // convert out args and result + // NOTE: this is the total number of native params, not just the args + // Convert independent params only. + // When we later convert the dependent params (if any) we will know that + // the params upon which they depend will have already been converted - + // regardless of ordering. + + foundDependentParam = false; + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + MOZ_ASSERT(!param.IsShared(), "[shared] implies [noscript]!"); + if (!param.IsOut() || !nativeParams[i].val.p) { + continue; + } + + const nsXPTType& type = param.GetType(); + if (type.IsDependent()) { + foundDependentParam = true; + continue; + } + + RootedValue val(cx); + + if (¶m == info->GetRetval()) { + val = rval; + } else if (argv[i].isPrimitive()) { + break; + } else { + RootedObject obj(cx, &argv[i].toObject()); + if (!JS_GetPropertyById( + cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) { + break; + } + } + + // setup allocator and/or iid + + const nsXPTType& inner = type.InnermostType(); + if (inner.Tag() == nsXPTType::T_INTERFACE) { + if (!inner.GetInterface()) { + break; + } + param_iid = inner.GetInterface()->IID(); + } + + MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect"); + if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type, + ¶m_iid, 0, nullptr)) + break; + } + + // if any params were dependent, then we must iterate again to convert them. + if (foundDependentParam && i == paramCount) { + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->GetParam(i); + if (!param.IsOut()) { + continue; + } + + const nsXPTType& type = param.GetType(); + if (!type.IsDependent()) { + continue; + } + + RootedValue val(cx); + uint32_t array_count; + + if (¶m == info->GetRetval()) { + val = rval; + } else { + RootedObject obj(cx, &argv[i].toObject()); + if (!JS_GetPropertyById( + cx, obj, xpcrt->GetStringID(XPCJSContext::IDX_VALUE), &val)) { + break; + } + } + + // setup allocator and/or iid + + if (!GetInterfaceTypeFromParam(info, type, nativeParams, ¶m_iid) || + !GetArraySizeFromParam(info, type, nativeParams, &array_count)) + break; + + MOZ_ASSERT(param.IsIndirect(), "outparams are always indirect"); + if (!XPCConvert::JSData2Native(cx, nativeParams[i].val.p, val, type, + ¶m_iid, array_count, nullptr)) + break; + } + } + + if (i != paramCount) { + // We didn't manage all the result conversions! + // We have to cleanup any junk that *did* get converted. + CleanupOutparams(info, nativeParams, /* inOutOnly = */ false, i); + } else { + // set to whatever the JS code might have set as the result + retval = xpccx->GetPendingResult(); + } + + return retval; +} + +static const JSClass XPCOutParamClass = {"XPCOutParam", 0, JS_NULL_CLASS_OPS}; + +bool xpc::IsOutObject(JSContext* cx, JSObject* obj) { + return JS::GetClass(obj) == &XPCOutParamClass; +} + +JSObject* xpc::NewOutObject(JSContext* cx) { + return JS_NewObject(cx, &XPCOutParamClass); +} + +// static +void nsXPCWrappedJS::DebugDumpInterfaceInfo(const nsXPTInterfaceInfo* aInfo, + int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("nsXPTInterfaceInfo @ %p = ", aInfo)); + XPC_LOG_INDENT(); + const char* name = aInfo->Name(); + XPC_LOG_ALWAYS(("interface name is %s", name)); + auto iid = aInfo->IID().ToString(); + XPC_LOG_ALWAYS(("IID number is %s", iid.get())); + XPC_LOG_ALWAYS(("InterfaceInfo @ %p", aInfo)); + uint16_t methodCount = 0; + if (depth) { + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("parent @ %p", aInfo->GetParent())); + methodCount = aInfo->MethodCount(); + XPC_LOG_ALWAYS(("MethodCount = %d", methodCount)); + XPC_LOG_ALWAYS(("ConstantCount = %d", aInfo->ConstantCount())); + XPC_LOG_OUTDENT(); + } + XPC_LOG_ALWAYS(("method count = %d", methodCount)); + if (depth && methodCount) { + depth--; + XPC_LOG_INDENT(); + for (uint16_t i = 0; i < methodCount; i++) { + XPC_LOG_ALWAYS(("Method %d is %s%s", i, + aInfo->Method(i).IsReflectable() ? "" : " NOT ", + "reflectable")); + } + XPC_LOG_OUTDENT(); + depth++; + } + XPC_LOG_OUTDENT(); +#endif +} diff --git a/js/xpconnect/src/XPCWrappedJSIterator.cpp b/js/xpconnect/src/XPCWrappedJSIterator.cpp new file mode 100644 index 0000000000..a901050a4b --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJSIterator.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" + +#include "mozilla/ResultExtensions.h" +#include "mozilla/dom/IteratorResultBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ScriptSettings.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpc; + +NS_IMPL_CYCLE_COLLECTION(XPCWrappedJSIterator, mEnum, mGlobal, mNext) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCWrappedJSIterator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(XPCWrappedJSIterator) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCWrappedJSIterator) + NS_INTERFACE_MAP_ENTRY(nsISimpleEnumerator) + NS_INTERFACE_MAP_ENTRY(nsISimpleEnumeratorBase) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, XPCWrappedJSIterator) +NS_INTERFACE_MAP_END + +XPCWrappedJSIterator::XPCWrappedJSIterator(nsIJSEnumerator* aEnum) + : mEnum(aEnum) { + nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(aEnum); + MOZ_ASSERT(wrapped); + mGlobal = NativeGlobal(wrapped->GetJSObjectGlobal()); +} + +nsresult XPCWrappedJSIterator::HasMoreElements(bool* aRetVal) { + if (mHasNext.isNothing()) { + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(mGlobal)); + + JSContext* cx = jsapi.cx(); + + JS::RootedValue val(cx); + MOZ_TRY(mEnum->Next(cx, &val)); + + RootedDictionary<IteratorResult> result(cx); + if (!result.Init(cx, val)) { + return NS_ERROR_FAILURE; + } + + if (!result.mDone) { + if (result.mValue.isObject()) { + JS::RootedObject obj(cx, &result.mValue.toObject()); + + nsresult rv; + if (!XPCConvert::JSObject2NativeInterface(cx, getter_AddRefs(mNext), + obj, &NS_GET_IID(nsISupports), + nullptr, &rv)) { + return rv; + } + } else { + mNext = XPCVariant::newVariant(cx, result.mValue); + } + } + mHasNext = Some(!result.mDone); + } + *aRetVal = *mHasNext; + return NS_OK; +} + +nsresult XPCWrappedJSIterator::GetNext(nsISupports** aRetVal) { + bool hasMore; + MOZ_TRY(HasMoreElements(&hasMore)); + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + mNext.forget(aRetVal); + mHasNext = Nothing(); + return NS_OK; +} + +nsresult XPCWrappedJSIterator::Iterator(nsIJSEnumerator** aRetVal) { + nsCOMPtr<nsIJSEnumerator> jsEnum = mEnum; + jsEnum.forget(aRetVal); + return NS_OK; +} + +nsresult XPCWrappedJSIterator::Entries(const nsID&, nsIJSEnumerator** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp new file mode 100644 index 0000000000..21f5c3e003 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -0,0 +1,1839 @@ +/* -*- 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/. */ + +/* Wrapper object for reflecting native xpcom objects into JavaScript. */ + +#include "xpcprivate.h" +#include "XPCMaps.h" +#include "nsWrapperCacheInlines.h" +#include "XPCLog.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject +#include "js/experimental/TypedData.h" // JS_GetTypedArrayLength, JS_IsTypedArrayObject +#include "js/MemoryFunctions.h" +#include "js/Object.h" // JS::GetPrivate, JS::SetPrivate, JS::SetReservedSlot +#include "js/Printf.h" +#include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetPropertyById, JS_SetProperty, JS_SetPropertyById +#include "jsfriendapi.h" +#include "AccessCheck.h" +#include "WrapperFactory.h" +#include "XrayWrapper.h" + +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" + +#include <new> +#include <stdint.h> +#include "mozilla/DeferredFinalize.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" +#include "mozilla/Sprintf.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/ProfilerLabels.h" +#include <algorithm> + +using namespace xpc; +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +/***************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) + +// No need to unlink the JS objects: if the XPCWrappedNative is cycle +// collected then its mFlatJSObject will be cycle collected too and +// finalization of the mFlatJSObject will unlink the JS objects (see +// XPC_WN_NoHelper_Finalize and FlatJSObjectFinalized). +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPCWrappedNative) + tmp->ExpireWrapper(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(XPCWrappedNative) + if (!tmp->IsValid()) { + return NS_OK; + } + + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[72]; + nsCOMPtr<nsIXPCScriptable> scr = tmp->GetScriptable(); + if (scr) { + SprintfLiteral(name, "XPCWrappedNative (%s)", scr->GetJSClass()->name); + } else { + SprintfLiteral(name, "XPCWrappedNative"); + } + + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(XPCWrappedNative, tmp->mRefCnt.get()) + } + + if (tmp->HasExternalReference()) { + // If our refcount is > 1, our reference to the flat JS object is + // considered "strong", and we're going to traverse it. + // + // If our refcount is <= 1, our reference to the flat JS object is + // considered "weak", and we're *not* going to traverse it. + // + // This reasoning is in line with the slightly confusing lifecycle rules + // for XPCWrappedNatives, described in a larger comment below and also + // on our wiki at http://wiki.mozilla.org/XPConnect_object_wrapping + + JSObject* obj = tmp->GetFlatJSObjectPreserveColor(); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFlatJSObject"); + cb.NoteJSChild(JS::GCCellPtr(obj)); + } + + // XPCWrappedNative keeps its native object alive. + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mIdentity"); + cb.NoteXPCOMChild(tmp->GetIdentityObject()); + + tmp->NoteTearoffs(cb); + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void XPCWrappedNative::Suspect(nsCycleCollectionNoteRootCallback& cb) { + if (!IsValid() || IsWrapperExpired()) { + return; + } + + MOZ_ASSERT(NS_IsMainThread(), + "Suspecting wrapped natives from non-main thread"); + + // Only record objects that might be part of a cycle as roots, unless + // the callback wants all traces (a debug feature). Do this even if + // the XPCWN doesn't own the JS reflector object in case the reflector + // keeps alive other C++ things. This is safe because if the reflector + // had died the reference from the XPCWN to it would have been cleared. + JSObject* obj = GetFlatJSObjectPreserveColor(); + if (JS::ObjectIsMarkedGray(obj) || cb.WantAllTraces()) { + cb.NoteJSRoot(obj); + } +} + +void XPCWrappedNative::NoteTearoffs(nsCycleCollectionTraversalCallback& cb) { + // Tearoffs hold their native object alive. If their JS object hasn't been + // finalized yet we'll note the edge between the JS object and the native + // (see nsXPConnect::Traverse), but if their JS object has been finalized + // then the tearoff is only reachable through the XPCWrappedNative, so we + // record an edge here. + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; + to = to->GetNextTearOff()) { + JSObject* jso = to->GetJSObjectPreserveColor(); + if (!jso) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "tearoff's mNative"); + cb.NoteXPCOMChild(to->GetNative()); + } + } +} + +#ifdef XPC_CHECK_CLASSINFO_CLAIMS +static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper); +#else +# define DEBUG_CheckClassInfoClaims(wrapper) ((void)0) +#endif + +/***************************************************************************/ +static nsresult FinishCreate(JSContext* cx, XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + nsWrapperCache* cache, XPCWrappedNative* inWrapper, + XPCWrappedNative** resultWrapper); + +// static +// +// This method handles the special case of wrapping a new global object. +// +// The normal code path for wrapping natives goes through +// XPCConvert::NativeInterface2JSObject, XPCWrappedNative::GetNewOrUsed, +// and finally into XPCWrappedNative::Init. Unfortunately, this path assumes +// very early on that we have an XPCWrappedNativeScope and corresponding global +// JS object, which are the very things we need to create here. So we special- +// case the logic and do some things in a different order. +nsresult XPCWrappedNative::WrapNewGlobal(JSContext* cx, + xpcObjectHelper& nativeHelper, + nsIPrincipal* principal, + bool initStandardClasses, + JS::RealmOptions& aOptions, + XPCWrappedNative** wrappedGlobal) { + nsCOMPtr<nsISupports> identity = do_QueryInterface(nativeHelper.Object()); + + // The object should specify that it's meant to be global. + MOZ_ASSERT(nativeHelper.GetScriptableFlags() & + XPC_SCRIPTABLE_IS_GLOBAL_OBJECT); + + // We shouldn't be reusing globals. + MOZ_ASSERT(!nativeHelper.GetWrapperCache() || + !nativeHelper.GetWrapperCache()->GetWrapperPreserveColor()); + + // Get the nsIXPCScriptable. This will tell us the JSClass of the object + // we're going to create. + nsCOMPtr<nsIXPCScriptable> scrProto; + nsCOMPtr<nsIXPCScriptable> scrWrapper; + GatherScriptable(identity, nativeHelper.GetClassInfo(), + getter_AddRefs(scrProto), getter_AddRefs(scrWrapper)); + MOZ_ASSERT(scrWrapper); + + // Finally, we get to the JSClass. + const JSClass* clasp = scrWrapper->GetJSClass(); + MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL); + + // Create the global. + aOptions.creationOptions().setTrace(XPCWrappedNative::Trace); + xpc::SetPrefableRealmOptions(aOptions); + + RootedObject global(cx, + xpc::CreateGlobalObject(cx, clasp, principal, aOptions)); + if (!global) { + return NS_ERROR_FAILURE; + } + XPCWrappedNativeScope* scope = ObjectScope(global); + + // Immediately enter the global's realm, so that everything else we + // create ends up there. + JSAutoRealm ar(cx, global); + + // If requested, initialize the standard classes on the global. + if (initStandardClasses && !JS::InitRealmStandardClasses(cx)) { + return NS_ERROR_FAILURE; + } + + // Make a proto. + XPCWrappedNativeProto* proto = XPCWrappedNativeProto::GetNewOrUsed( + cx, scope, nativeHelper.GetClassInfo(), scrProto); + if (!proto) { + return NS_ERROR_FAILURE; + } + + // Set up the prototype on the global. + MOZ_ASSERT(proto->GetJSProtoObject()); + RootedObject protoObj(cx, proto->GetJSProtoObject()); + bool success = JS_SetPrototype(cx, global, protoObj); + if (!success) { + return NS_ERROR_FAILURE; + } + + // Construct the wrapper, which takes over the strong reference to the + // native object. + RefPtr<XPCWrappedNative> wrapper = + new XPCWrappedNative(std::move(identity), proto); + + // + // We don't call ::Init() on this wrapper, because our setup requirements + // are different for globals. We do our setup inline here, instead. + // + + wrapper->mScriptable = scrWrapper; + + // Set the JS object to the global we already created. + wrapper->SetFlatJSObject(global); + + // Set the reserved slot to the XPCWrappedNative. + static_assert(JSCLASS_GLOBAL_APPLICATION_SLOTS > 0, + "Need at least one slot for JSCLASS_SLOT0_IS_NSISUPPORTS"); + JS::SetObjectISupports(global, wrapper); + + // There are dire comments elsewhere in the code about how a GC can + // happen somewhere after wrapper initialization but before the wrapper is + // added to the hashtable in FinishCreate(). It's not clear if that can + // happen here, but let's just be safe for now. + AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); + + // Call the common Init finish routine. This mainly just does an AddRef + // on behalf of XPConnect (the corresponding Release is in the finalizer + // hook), but it does some other miscellaneous things too, so we don't + // inline it. + success = wrapper->FinishInit(cx); + MOZ_ASSERT(success); + + // Go through some extra work to find the tearoff. This is kind of silly + // on a conceptual level: the point of tearoffs is to cache the results + // of QI-ing mIdentity to different interfaces, and we don't need that + // since we're dealing with nsISupports. But lots of code expects tearoffs + // to exist for everything, so we just follow along. + RefPtr<XPCNativeInterface> iface = + XPCNativeInterface::GetNewOrUsed(cx, &NS_GET_IID(nsISupports)); + MOZ_ASSERT(iface); + nsresult status; + success = wrapper->FindTearOff(cx, iface, false, &status); + if (!success) { + return status; + } + + // Call the common creation finish routine. This does all of the bookkeeping + // like inserting the wrapper into the wrapper map and setting up the wrapper + // cache. + nsresult rv = FinishCreate(cx, scope, iface, nativeHelper.GetWrapperCache(), + wrapper, wrappedGlobal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// static +nsresult XPCWrappedNative::GetNewOrUsed(JSContext* cx, xpcObjectHelper& helper, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** resultWrapper) { + MOZ_ASSERT(Interface); + nsWrapperCache* cache = helper.GetWrapperCache(); + + MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor(), + "We assume the caller already checked if it could get the " + "wrapper from the cache."); + + nsresult rv; + + MOZ_ASSERT(!Scope->GetRuntime()->GCIsRunning(), + "XPCWrappedNative::GetNewOrUsed called during GC"); + + nsCOMPtr<nsISupports> identity = do_QueryInterface(helper.Object()); + + if (!identity) { + NS_ERROR("This XPCOM object fails in QueryInterface to nsISupports!"); + return NS_ERROR_FAILURE; + } + + RefPtr<XPCWrappedNative> wrapper; + + Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); + // Some things are nsWrapperCache subclasses but never use the cache, so go + // ahead and check our map even if we have a cache and it has no existing + // wrapper: we might have an XPCWrappedNative anyway. + wrapper = map->Find(identity); + + if (wrapper) { + if (!wrapper->FindTearOff(cx, Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + wrapper.forget(resultWrapper); + return NS_OK; + } + + // There is a chance that the object wants to have the self-same JSObject + // reflection regardless of the scope into which we are reflecting it. + // Many DOM objects require this. The scriptable helper specifies this + // in preCreate by indicating a 'parent' of a particular scope. + // + // To handle this we need to get the scriptable helper early and ask it. + // It is possible that we will then end up forwarding this entire call + // to this same function but with a different scope. + + // If we are making a wrapper for an nsIClassInfo singleton then + // We *don't* want to have it use the prototype meant for instances + // of that class. + uint32_t classInfoFlags; + bool isClassInfoSingleton = + helper.GetClassInfo() == helper.Object() && + NS_SUCCEEDED(helper.GetClassInfo()->GetFlags(&classInfoFlags)) && + (classInfoFlags & nsIClassInfo::SINGLETON_CLASSINFO); + + nsIClassInfo* info = helper.GetClassInfo(); + + nsCOMPtr<nsIXPCScriptable> scrProto; + nsCOMPtr<nsIXPCScriptable> scrWrapper; + + // Gather scriptable create info if we are wrapping something + // other than an nsIClassInfo object. We need to not do this for + // nsIClassInfo objects because often nsIClassInfo implementations + // are also nsIXPCScriptable helper implementations, but the helper + // code is obviously intended for the implementation of the class + // described by the nsIClassInfo, not for the class info object + // itself. + if (!isClassInfoSingleton) { + GatherScriptable(identity, info, getter_AddRefs(scrProto), + getter_AddRefs(scrWrapper)); + } + + RootedObject parent(cx, Scope->GetGlobalForWrappedNatives()); + + mozilla::Maybe<JSAutoRealm> ar; + + if (scrWrapper && scrWrapper->WantPreCreate()) { + RootedObject plannedParent(cx, parent); + nsresult rv = scrWrapper->PreCreate(identity, cx, parent, parent.address()); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_OK; + + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), + "Xray wrapper being used to parent XPCWrappedNative?"); + + MOZ_ASSERT(JS_IsGlobalObject(parent), + "Non-global being used to parent XPCWrappedNative?"); + + ar.emplace(static_cast<JSContext*>(cx), parent); + + if (parent != plannedParent) { + XPCWrappedNativeScope* betterScope = ObjectScope(parent); + MOZ_ASSERT(betterScope != Scope, + "How can we have the same scope for two different globals?"); + return GetNewOrUsed(cx, helper, betterScope, Interface, resultWrapper); + } + + // Take the performance hit of checking the hashtable again in case + // the preCreate call caused the wrapper to get created through some + // interesting path (the DOM code tends to make this happen sometimes). + + if (cache) { + RootedObject cached(cx, cache->GetWrapper()); + if (cached) { + wrapper = XPCWrappedNative::Get(cached); + } + } else { + wrapper = map->Find(identity); + } + + if (wrapper) { + if (!wrapper->FindTearOff(cx, Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + wrapper.forget(resultWrapper); + return NS_OK; + } + } else { + ar.emplace(static_cast<JSContext*>(cx), parent); + } + + AutoMarkingWrappedNativeProtoPtr proto(cx); + + // If there is ClassInfo (and we are not building a wrapper for the + // nsIClassInfo interface) then we use a wrapper that needs a prototype. + + // Note that the security check happens inside FindTearOff - after the + // wrapper is actually created, but before JS code can see it. + + if (info && !isClassInfoSingleton) { + proto = XPCWrappedNativeProto::GetNewOrUsed(cx, Scope, info, scrProto); + if (!proto) { + return NS_ERROR_FAILURE; + } + + wrapper = new XPCWrappedNative(std::move(identity), proto); + } else { + RefPtr<XPCNativeInterface> iface = Interface; + if (!iface) { + iface = XPCNativeInterface::GetISupports(cx); + } + + XPCNativeSetKey key(cx, iface); + RefPtr<XPCNativeSet> set = XPCNativeSet::GetNewOrUsed(cx, &key); + + if (!set) { + return NS_ERROR_FAILURE; + } + + wrapper = new XPCWrappedNative(std::move(identity), Scope, set.forget()); + } + + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), + "Xray wrapper being used to parent XPCWrappedNative?"); + + // We use an AutoMarkingPtr here because it is possible for JS gc to happen + // after we have Init'd the wrapper but *before* we add it to the hashtable. + // This would cause the mSet to get collected and we'd later crash. I've + // *seen* this happen. + AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); + + if (!wrapper->Init(cx, scrWrapper)) { + return NS_ERROR_FAILURE; + } + + if (!wrapper->FindTearOff(cx, Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + + return FinishCreate(cx, Scope, Interface, cache, wrapper, resultWrapper); +} + +static nsresult FinishCreate(JSContext* cx, XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + nsWrapperCache* cache, XPCWrappedNative* inWrapper, + XPCWrappedNative** resultWrapper) { + MOZ_ASSERT(inWrapper); + + Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); + + RefPtr<XPCWrappedNative> wrapper; + // Deal with the case where the wrapper got created as a side effect + // of one of our calls out of this code. Add() returns the (possibly + // pre-existing) wrapper that ultimately ends up in the map, which is + // what we want. + wrapper = map->Add(inWrapper); + if (!wrapper) { + return NS_ERROR_FAILURE; + } + + if (wrapper == inWrapper) { + JSObject* flat = wrapper->GetFlatJSObject(); + MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor() || + flat == cache->GetWrapperPreserveColor(), + "This object has a cached wrapper that's different from " + "the JSObject held by its native wrapper?"); + + if (cache && !cache->GetWrapperPreserveColor()) { + cache->SetWrapper(flat); + } + } + + DEBUG_CheckClassInfoClaims(wrapper); + wrapper.forget(resultWrapper); + return NS_OK; +} + +// This ctor is used if this object will have a proto. +XPCWrappedNative::XPCWrappedNative(nsCOMPtr<nsISupports>&& aIdentity, + XPCWrappedNativeProto* aProto) + : mMaybeProto(aProto), mSet(aProto->GetSet()) { + MOZ_ASSERT(NS_IsMainThread()); + + mIdentity = aIdentity; + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + + MOZ_ASSERT(mMaybeProto, "bad ctor param"); + MOZ_ASSERT(mSet, "bad ctor param"); +} + +// This ctor is used if this object will NOT have a proto. +XPCWrappedNative::XPCWrappedNative(nsCOMPtr<nsISupports>&& aIdentity, + XPCWrappedNativeScope* aScope, + RefPtr<XPCNativeSet>&& aSet) + : mMaybeScope(TagScope(aScope)), mSet(std::move(aSet)) { + MOZ_ASSERT(NS_IsMainThread()); + + mIdentity = aIdentity; + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + + MOZ_ASSERT(aScope, "bad ctor param"); + MOZ_ASSERT(mSet, "bad ctor param"); +} + +XPCWrappedNative::~XPCWrappedNative() { Destroy(); } + +void XPCWrappedNative::Destroy() { + mScriptable = nullptr; + +#ifdef DEBUG + // Check that this object has already been swept from the map. + XPCWrappedNativeScope* scope = GetScope(); + if (scope) { + Native2WrappedNativeMap* map = scope->GetWrappedNativeMap(); + MOZ_ASSERT(map->Find(GetIdentityObject()) != this); + } +#endif + + if (mIdentity) { + XPCJSRuntime* rt = GetRuntime(); + if (rt && rt->GetDoingFinalization()) { + DeferredFinalize(mIdentity.forget().take()); + } else { + mIdentity = nullptr; + } + } + + mMaybeScope = nullptr; +} + +// A hack for bug 517665, increase the probability for GC. +// TODO: Try removing this and just using the actual size of the object. +static const size_t GCMemoryFactor = 2; + +inline void XPCWrappedNative::SetFlatJSObject(JSObject* object) { + MOZ_ASSERT(!mFlatJSObject); + MOZ_ASSERT(object); + + JS::AddAssociatedMemory(object, sizeof(*this) * GCMemoryFactor, + JS::MemoryUse::XPCWrappedNative); + + mFlatJSObject = object; + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); +} + +inline void XPCWrappedNative::UnsetFlatJSObject() { + MOZ_ASSERT(mFlatJSObject); + + JS::RemoveAssociatedMemory(mFlatJSObject.unbarrieredGetPtr(), + sizeof(*this) * GCMemoryFactor, + JS::MemoryUse::XPCWrappedNative); + + mFlatJSObject = nullptr; + mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); +} + +// This is factored out so that it can be called publicly. +// static +nsIXPCScriptable* XPCWrappedNative::GatherProtoScriptable( + nsIClassInfo* classInfo) { + MOZ_ASSERT(classInfo, "bad param"); + + nsCOMPtr<nsIXPCScriptable> helper; + nsresult rv = classInfo->GetScriptableHelper(getter_AddRefs(helper)); + if (NS_SUCCEEDED(rv) && helper) { + return helper; + } + + return nullptr; +} + +// static +void XPCWrappedNative::GatherScriptable(nsISupports* aObj, + nsIClassInfo* aClassInfo, + nsIXPCScriptable** aScrProto, + nsIXPCScriptable** aScrWrapper) { + MOZ_ASSERT(!*aScrProto, "bad param"); + MOZ_ASSERT(!*aScrWrapper, "bad param"); + + nsCOMPtr<nsIXPCScriptable> scrProto; + nsCOMPtr<nsIXPCScriptable> scrWrapper; + + // Get the class scriptable helper (if present) + if (aClassInfo) { + scrProto = GatherProtoScriptable(aClassInfo); + } + + // Do the same for the wrapper specific scriptable + scrWrapper = do_QueryInterface(aObj); + if (scrWrapper) { + // A whole series of assertions to catch bad uses of scriptable flags on + // the scrWrapper... + + // Can't set WANT_PRECREATE on an instance scriptable without also + // setting it on the class scriptable. + MOZ_ASSERT_IF(scrWrapper->WantPreCreate(), + scrProto && scrProto->WantPreCreate()); + + // Can't set DONT_ENUM_QUERY_INTERFACE on an instance scriptable + // without also setting it on the class scriptable (if present). + MOZ_ASSERT_IF(scrWrapper->DontEnumQueryInterface() && scrProto, + scrProto->DontEnumQueryInterface()); + + // Can't set ALLOW_PROP_MODS_DURING_RESOLVE on an instance scriptable + // without also setting it on the class scriptable (if present). + MOZ_ASSERT_IF(scrWrapper->AllowPropModsDuringResolve() && scrProto, + scrProto->AllowPropModsDuringResolve()); + } else { + scrWrapper = scrProto; + } + + scrProto.forget(aScrProto); + scrWrapper.forget(aScrWrapper); +} + +bool XPCWrappedNative::Init(JSContext* cx, nsIXPCScriptable* aScriptable) { + // Setup our scriptable... + MOZ_ASSERT(!mScriptable); + mScriptable = aScriptable; + + // create our flatJSObject + + const JSClass* jsclazz = + mScriptable ? mScriptable->GetJSClass() : &XPC_WN_NoHelper_JSClass; + + // We should have the global jsclass flag if and only if we're a global. + MOZ_ASSERT_IF(mScriptable, !!mScriptable->IsGlobalObject() == + !!(jsclazz->flags & JSCLASS_IS_GLOBAL)); + + MOZ_ASSERT(jsclazz && jsclazz->name && jsclazz->flags && + jsclazz->getResolve() && jsclazz->hasFinalize(), + "bad class"); + + RootedObject protoJSObject(cx, HasProto() ? GetProto()->GetJSProtoObject() + : JS::GetRealmObjectPrototype(cx)); + if (!protoJSObject) { + return false; + } + + JSObject* object = JS_NewObjectWithGivenProto(cx, jsclazz, protoJSObject); + if (!object) { + return false; + } + + SetFlatJSObject(object); + + JS::SetObjectISupports(mFlatJSObject, this); + + return FinishInit(cx); +} + +bool XPCWrappedNative::FinishInit(JSContext* cx) { + // This reference will be released when mFlatJSObject is finalized. + // Since this reference will push the refcount to 2 it will also root + // mFlatJSObject; + MOZ_ASSERT(1 == mRefCnt, "unexpected refcount value"); + NS_ADDREF(this); + + return true; +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCWrappedNative) + NS_INTERFACE_MAP_ENTRY(nsIXPConnectWrappedNative) + NS_INTERFACE_MAP_ENTRY(nsIXPConnectJSObjectHolder) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPConnectWrappedNative) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCWrappedNative) + +// Release calls Destroy() immediately when the refcount drops to 0 to +// clear the weak references nsXPConnect has to XPCWNs and to ensure there +// are no pointers to dying protos. +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(XPCWrappedNative, Destroy()) + +/* + * Wrapped Native lifetime management is messy! + * + * - At creation we push the refcount to 2 (only one of which is owned by + * the native caller that caused the wrapper creation). + * - During the JS GC Mark phase we mark any wrapper with a refcount > 1. + * - The *only* thing that can make the wrapper get destroyed is the + * finalization of mFlatJSObject. And *that* should only happen if the only + * reference is the single extra (internal) reference we hold. + * + * - The wrapper has a pointer to the nsISupports 'view' of the wrapped native + * object i.e... mIdentity. This is held until the wrapper's refcount goes + * to zero and the wrapper is released, or until an expired wrapper (i.e., + * one unlinked by the cycle collector) has had its JS object finalized. + * + * - The wrapper also has 'tearoffs'. It has one tearoff for each interface + * that is actually used on the native object. 'Used' means we have either + * needed to QueryInterface to verify the availability of that interface + * of that we've had to QueryInterface in order to actually make a call + * into the wrapped object via the pointer for the given interface. + * + * - Each tearoff's 'mNative' member (if non-null) indicates one reference + * held by our wrapper on the wrapped native for the given interface + * associated with the tearoff. If we release that reference then we set + * the tearoff's 'mNative' to null. + * + * - We use the occasion of the JavaScript GCCallback for the JSGC_MARK_END + * event to scan the tearoffs of all wrappers for non-null mNative members + * that represent unused references. We can tell that a given tearoff's + * mNative is unused by noting that no live XPCCallContexts hold a pointer + * to the tearoff. + * + * - As a time/space tradeoff we may decide to not do this scanning on + * *every* JavaScript GC. We *do* want to do this *sometimes* because + * we want to allow for wrapped native's to do their own tearoff patterns. + * So, we want to avoid holding references to interfaces that we don't need. + * At the same time, we don't want to be bracketing every call into a + * wrapped native object with a QueryInterface/Release pair. And we *never* + * make a call into the object except via the correct interface for which + * we've QI'd. + * + * - Each tearoff *can* have a mJSObject whose lazily resolved properties + * represent the methods/attributes/constants of that specific interface. + * This is optionally reflected into JavaScript as "foo.nsIFoo" when "foo" + * is the name of mFlatJSObject and "nsIFoo" is the name of the given + * interface associated with the tearoff. When we create the tearoff's + * mJSObject we set it's parent to be mFlatJSObject. This way we know that + * when mFlatJSObject get's collected there are no outstanding reachable + * tearoff mJSObjects. Note that we must clear the private of any lingering + * mJSObjects at this point because we have no guarentee of the *order* of + * finalization within a given gc cycle. + */ + +void XPCWrappedNative::FlatJSObjectFinalized() { + if (!IsValid()) { + return; + } + + // Iterate the tearoffs and null out each of their JSObject's privates. + // This will keep them from trying to access their pointers to the + // dying tearoff object. We can safely assume that those remaining + // JSObjects are about to be finalized too. + + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; + to = to->GetNextTearOff()) { + JSObject* jso = to->GetJSObjectPreserveColor(); + if (jso) { + JS::SetReservedSlot(jso, XPCWrappedNativeTearOff::TearOffSlot, + JS::UndefinedValue()); + to->JSObjectFinalized(); + } + + // We also need to release any native pointers held... + RefPtr<nsISupports> native = to->TakeNative(); + if (native && GetRuntime()) { + DeferredFinalize(native.forget().take()); + } + + to->SetInterface(nullptr); + } + + nsWrapperCache* cache = nullptr; + CallQueryInterface(mIdentity, &cache); + if (cache) { + cache->ClearWrapper(mFlatJSObject.unbarrieredGetPtr()); + } + + UnsetFlatJSObject(); + + MOZ_ASSERT(mIdentity, "bad pointer!"); + + if (IsWrapperExpired()) { + Destroy(); + } + + // Note that it's not safe to touch mNativeWrapper here since it's + // likely that it has already been finalized. + + Release(); +} + +void XPCWrappedNative::FlatJSObjectMoved(JSObject* obj, const JSObject* old) { + JS::AutoAssertGCCallback inCallback; + MOZ_ASSERT(mFlatJSObject == old); + + nsWrapperCache* cache = nullptr; + CallQueryInterface(mIdentity, &cache); + if (cache) { + cache->UpdateWrapper(obj, old); + } + + mFlatJSObject = obj; +} + +void XPCWrappedNative::SystemIsBeingShutDown() { + if (!IsValid()) { + return; + } + + // The long standing strategy is to leak some objects still held at shutdown. + // The general problem is that propagating release out of xpconnect at + // shutdown time causes a world of problems. + + // We leak mIdentity (see above). + + // Short circuit future finalization. + JS::SetObjectISupports(mFlatJSObject, nullptr); + UnsetFlatJSObject(); + + XPCWrappedNativeProto* proto = GetProto(); + + if (HasProto()) { + proto->SystemIsBeingShutDown(); + } + + // We don't clear mScriptable here. The destructor will do it. + + // Cleanup the tearoffs. + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; + to = to->GetNextTearOff()) { + if (JSObject* jso = to->GetJSObjectPreserveColor()) { + JS::SetReservedSlot(jso, XPCWrappedNativeTearOff::TearOffSlot, + JS::UndefinedValue()); + to->SetJSObject(nullptr); + } + // We leak the tearoff mNative + // (for the same reason we leak mIdentity - see above). + Unused << to->TakeNative().take(); + to->SetInterface(nullptr); + } +} + +/***************************************************************************/ + +bool XPCWrappedNative::ExtendSet(JSContext* aCx, + XPCNativeInterface* aInterface) { + if (!mSet->HasInterface(aInterface)) { + XPCNativeSetKey key(mSet, aInterface); + RefPtr<XPCNativeSet> newSet = XPCNativeSet::GetNewOrUsed(aCx, &key); + if (!newSet) { + return false; + } + + mSet = std::move(newSet); + } + return true; +} + +XPCWrappedNativeTearOff* XPCWrappedNative::FindTearOff( + JSContext* cx, XPCNativeInterface* aInterface, + bool needJSObject /* = false */, nsresult* pError /* = nullptr */) { + nsresult rv = NS_OK; + XPCWrappedNativeTearOff* to; + XPCWrappedNativeTearOff* firstAvailable = nullptr; + + XPCWrappedNativeTearOff* lastTearOff; + for (lastTearOff = to = &mFirstTearOff; to; + lastTearOff = to, to = to->GetNextTearOff()) { + if (to->GetInterface() == aInterface) { + if (needJSObject && !to->GetJSObjectPreserveColor()) { + AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); + bool ok = InitTearOffJSObject(cx, to); + // During shutdown, we don't sweep tearoffs. So make sure + // to unmark manually in case the auto-marker marked us. + // We shouldn't ever be getting here _during_ our + // Mark/Sweep cycle, so this should be safe. + to->Unmark(); + if (!ok) { + to = nullptr; + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + if (pError) { + *pError = rv; + } + return to; + } + if (!firstAvailable && to->IsAvailable()) { + firstAvailable = to; + } + } + + to = firstAvailable; + + if (!to) { + to = lastTearOff->AddTearOff(); + } + + { + // Scope keeps |tearoff| from leaking across the rest of the function. + AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); + rv = InitTearOff(cx, to, aInterface, needJSObject); + // During shutdown, we don't sweep tearoffs. So make sure to unmark + // manually in case the auto-marker marked us. We shouldn't ever be + // getting here _during_ our Mark/Sweep cycle, so this should be safe. + to->Unmark(); + if (NS_FAILED(rv)) { + to = nullptr; + } + } + + if (pError) { + *pError = rv; + } + return to; +} + +XPCWrappedNativeTearOff* XPCWrappedNative::FindTearOff(JSContext* cx, + const nsIID& iid) { + RefPtr<XPCNativeInterface> iface = XPCNativeInterface::GetNewOrUsed(cx, &iid); + return iface ? FindTearOff(cx, iface) : nullptr; +} + +nsresult XPCWrappedNative::InitTearOff(JSContext* cx, + XPCWrappedNativeTearOff* aTearOff, + XPCNativeInterface* aInterface, + bool needJSObject) { + // Determine if the object really does this interface... + + const nsIID* iid = aInterface->GetIID(); + nsISupports* identity = GetIdentityObject(); + + // This is an nsRefPtr instead of an nsCOMPtr because it may not be the + // canonical nsISupports for this object. + RefPtr<nsISupports> qiResult; + + // We are about to call out to other code. + // So protect our intended tearoff. + + aTearOff->SetReserved(); + + if (NS_FAILED(identity->QueryInterface(*iid, getter_AddRefs(qiResult))) || + !qiResult) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + + // Guard against trying to build a tearoff for a shared nsIClassInfo. + if (iid->Equals(NS_GET_IID(nsIClassInfo))) { + nsCOMPtr<nsISupports> alternate_identity(do_QueryInterface(qiResult)); + if (alternate_identity.get() != identity) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + } + + // Guard against trying to build a tearoff for an interface that is + // aggregated and is implemented as a nsIXPConnectWrappedJS using this + // self-same JSObject. The XBL system does this. If we mutate the set + // of this wrapper then we will shadow the method that XBL has added to + // the JSObject that it has inserted in the JS proto chain between our + // JSObject and our XPCWrappedNativeProto's JSObject. If we let this + // set mutation happen then the interface's methods will be added to + // our JSObject, but calls on those methods will get routed up to + // native code and into the wrappedJS - which will do a method lookup + // on *our* JSObject and find the same method and make another call + // into an infinite loop. + // see: http://bugzilla.mozilla.org/show_bug.cgi?id=96725 + + nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS(do_QueryInterface(qiResult)); + if (wrappedJS) { + RootedObject jso(cx, wrappedJS->GetJSObject()); + if (jso == mFlatJSObject) { + // The implementing JSObject is the same as ours! Just say OK + // without actually extending the set. + // + // XXX It is a little cheesy to have FindTearOff return an + // 'empty' tearoff. But this is the centralized place to do the + // QI activities on the underlying object. *And* most caller to + // FindTearOff only look for a non-null result and ignore the + // actual tearoff returned. The only callers that do use the + // returned tearoff make sure to check for either a non-null + // JSObject or a matching Interface before proceeding. + // I think we can get away with this bit of ugliness. + + aTearOff->SetInterface(nullptr); + return NS_OK; + } + } + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateWrapper( + cx, *iid, identity, GetClassInfo()))) { + // the security manager vetoed. It should have set an exception. + aTearOff->SetInterface(nullptr); + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + + // If this is not already in our set we need to extend our set. + // Note: we do not cache the result of the previous call to HasInterface() + // because we unlocked and called out in the interim and the result of the + // previous call might not be correct anymore. + + if (!mSet->HasInterface(aInterface) && !ExtendSet(cx, aInterface)) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + + aTearOff->SetInterface(aInterface); + aTearOff->SetNative(qiResult); + + if (needJSObject && !InitTearOffJSObject(cx, aTearOff)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +bool XPCWrappedNative::InitTearOffJSObject(JSContext* cx, + XPCWrappedNativeTearOff* to) { + JSObject* obj = JS_NewObject(cx, &XPC_WN_Tearoff_JSClass); + if (!obj) { + return false; + } + + JS::SetReservedSlot(obj, XPCWrappedNativeTearOff::TearOffSlot, + JS::PrivateValue(to)); + to->SetJSObject(obj); + + JS::SetReservedSlot(obj, XPCWrappedNativeTearOff::FlatObjectSlot, + JS::ObjectValue(*mFlatJSObject)); + return true; +} + +/***************************************************************************/ + +static bool Throw(nsresult errNum, XPCCallContext& ccx) { + XPCThrower::Throw(errNum, ccx); + return false; +} + +/***************************************************************************/ + +class MOZ_STACK_CLASS CallMethodHelper final { + XPCCallContext& mCallContext; + nsresult mInvokeResult; + const nsXPTInterfaceInfo* const mIFaceInfo; + const nsXPTMethodInfo* mMethodInfo; + nsISupports* const mCallee; + const uint16_t mVTableIndex; + HandleId mIdxValueId; + + AutoTArray<nsXPTCVariant, 8> mDispatchParams; + uint8_t mJSContextIndex; // TODO make const + uint8_t mOptArgcIndex; // TODO make const + + Value* const mArgv; + const uint32_t mArgc; + + MOZ_ALWAYS_INLINE bool GetArraySizeFromParam(const nsXPTType& type, + HandleValue maybeArray, + uint32_t* result); + + MOZ_ALWAYS_INLINE bool GetInterfaceTypeFromParam(const nsXPTType& type, + nsID* result) const; + + MOZ_ALWAYS_INLINE bool GetOutParamSource(uint8_t paramIndex, + MutableHandleValue srcp) const; + + MOZ_ALWAYS_INLINE bool GatherAndConvertResults(); + + MOZ_ALWAYS_INLINE bool QueryInterfaceFastPath(); + + nsXPTCVariant* GetDispatchParam(uint8_t paramIndex) { + if (paramIndex >= mJSContextIndex) { + paramIndex += 1; + } + if (paramIndex >= mOptArgcIndex) { + paramIndex += 1; + } + return &mDispatchParams[paramIndex]; + } + const nsXPTCVariant* GetDispatchParam(uint8_t paramIndex) const { + return const_cast<CallMethodHelper*>(this)->GetDispatchParam(paramIndex); + } + + MOZ_ALWAYS_INLINE bool InitializeDispatchParams(); + + MOZ_ALWAYS_INLINE bool ConvertIndependentParams(bool* foundDependentParam); + MOZ_ALWAYS_INLINE bool ConvertIndependentParam(uint8_t i); + MOZ_ALWAYS_INLINE bool ConvertDependentParams(); + MOZ_ALWAYS_INLINE bool ConvertDependentParam(uint8_t i); + + MOZ_ALWAYS_INLINE nsresult Invoke(); + + public: + explicit CallMethodHelper(XPCCallContext& ccx) + : mCallContext(ccx), + mInvokeResult(NS_ERROR_UNEXPECTED), + mIFaceInfo(ccx.GetInterface()->GetInterfaceInfo()), + mMethodInfo(nullptr), + mCallee(ccx.GetTearOff()->GetNative()), + mVTableIndex(ccx.GetMethodIndex()), + mIdxValueId(ccx.GetContext()->GetStringID(XPCJSContext::IDX_VALUE)), + mJSContextIndex(UINT8_MAX), + mOptArgcIndex(UINT8_MAX), + mArgv(ccx.GetArgv()), + mArgc(ccx.GetArgc()) + + { + // Success checked later. + mIFaceInfo->GetMethodInfo(mVTableIndex, &mMethodInfo); + } + + ~CallMethodHelper(); + + MOZ_ALWAYS_INLINE bool Call(); + + // Trace implementation so we can put our CallMethodHelper in a Rooted<T>. + void trace(JSTracer* aTrc); +}; + +// static +bool XPCWrappedNative::CallMethod(XPCCallContext& ccx, + CallMode mode /*= CALL_METHOD */) { + nsresult rv = ccx.CanCallNow(); + if (NS_FAILED(rv)) { + return Throw(rv, ccx); + } + + JS::Rooted<CallMethodHelper> helper(ccx, /* init = */ ccx); + return helper.get().Call(); +} + +bool CallMethodHelper::Call() { + mCallContext.SetRetVal(JS::UndefinedValue()); + + mCallContext.GetContext()->SetPendingException(nullptr); + + using Flags = js::ProfilingStackFrame::Flags; + if (mVTableIndex == 0) { + AUTO_PROFILER_LABEL_DYNAMIC_FAST(mIFaceInfo->Name(), "QueryInterface", DOM, + mCallContext.GetJSContext(), + uint32_t(Flags::STRING_TEMPLATE_METHOD) | + uint32_t(Flags::RELEVANT_FOR_JS)); + + return QueryInterfaceFastPath(); + } + + if (!mMethodInfo) { + Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, mCallContext); + return false; + } + + // Add profiler labels matching the WebIDL profiler labels, + // which also use the DOM category. + Flags templateFlag = Flags::STRING_TEMPLATE_METHOD; + if (mMethodInfo->IsGetter()) { + templateFlag = Flags::STRING_TEMPLATE_GETTER; + } + if (mMethodInfo->IsSetter()) { + templateFlag = Flags::STRING_TEMPLATE_SETTER; + } + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + mIFaceInfo->Name(), mMethodInfo->NameOrDescription(), DOM, + mCallContext.GetJSContext(), + uint32_t(templateFlag) | uint32_t(Flags::RELEVANT_FOR_JS)); + + if (!InitializeDispatchParams()) { + return false; + } + + // Iterate through the params doing conversions of independent params only. + // When we later convert the dependent params (if any) we will know that + // the params upon which they depend will have already been converted - + // regardless of ordering. + bool foundDependentParam = false; + if (!ConvertIndependentParams(&foundDependentParam)) { + return false; + } + + if (foundDependentParam && !ConvertDependentParams()) { + return false; + } + + mInvokeResult = Invoke(); + + if (JS_IsExceptionPending(mCallContext)) { + return false; + } + + if (NS_FAILED(mInvokeResult)) { + ThrowBadResult(mInvokeResult, mCallContext); + return false; + } + + return GatherAndConvertResults(); +} + +CallMethodHelper::~CallMethodHelper() { + for (nsXPTCVariant& param : mDispatchParams) { + uint32_t arraylen = 0; + if (!GetArraySizeFromParam(param.type, UndefinedHandleValue, &arraylen)) { + continue; + } + + xpc::DestructValue(param.type, ¶m.val, arraylen); + } +} + +bool CallMethodHelper::GetArraySizeFromParam(const nsXPTType& type, + HandleValue maybeArray, + uint32_t* result) { + if (type.Tag() != nsXPTType::T_LEGACY_ARRAY && + type.Tag() != nsXPTType::T_PSTRING_SIZE_IS && + type.Tag() != nsXPTType::T_PWSTRING_SIZE_IS) { + *result = 0; + return true; + } + + uint8_t argnum = type.ArgNum(); + uint32_t* lengthp = &GetDispatchParam(argnum)->val.u32; + + // TODO fixup the various exceptions that are thrown + + // If the array length wasn't passed, it might have been listed as optional. + // When converting arguments from JS to C++, we pass the array as + // |maybeArray|, and give ourselves the chance to infer the length. Once we + // have it, we stick it in the right slot so that we can find it again when + // cleaning up the params. from the array. + if (argnum >= mArgc && maybeArray.isObject()) { + MOZ_ASSERT(mMethodInfo->Param(argnum).IsOptional()); + RootedObject arrayOrNull(mCallContext, &maybeArray.toObject()); + + bool isArray; + bool ok = false; + if (JS::IsArrayObject(mCallContext, maybeArray, &isArray) && isArray) { + ok = JS::GetArrayLength(mCallContext, arrayOrNull, lengthp); + } else if (JS_IsTypedArrayObject(&maybeArray.toObject())) { + size_t len = JS_GetTypedArrayLength(&maybeArray.toObject()); + if (len <= UINT32_MAX) { + *lengthp = len; + ok = true; + } + } + + if (!ok) { + return Throw(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, mCallContext); + } + } + + *result = *lengthp; + return true; +} + +bool CallMethodHelper::GetInterfaceTypeFromParam(const nsXPTType& type, + nsID* result) const { + result->Clear(); + + const nsXPTType& inner = type.InnermostType(); + if (inner.Tag() == nsXPTType::T_INTERFACE) { + if (!inner.GetInterface()) { + return Throw(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, mCallContext); + } + + *result = inner.GetInterface()->IID(); + } else if (inner.Tag() == nsXPTType::T_INTERFACE_IS) { + const nsXPTCVariant* param = GetDispatchParam(inner.ArgNum()); + if (param->type.Tag() != nsXPTType::T_NSID && + param->type.Tag() != nsXPTType::T_NSIDPTR) { + return Throw(NS_ERROR_UNEXPECTED, mCallContext); + } + + const void* ptr = ¶m->val; + if (param->type.Tag() == nsXPTType::T_NSIDPTR) { + ptr = *static_cast<nsID* const*>(ptr); + } + + if (!ptr) { + return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, + inner.ArgNum(), mCallContext); + } + + *result = *static_cast<const nsID*>(ptr); + } + return true; +} + +bool CallMethodHelper::GetOutParamSource(uint8_t paramIndex, + MutableHandleValue srcp) const { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); + bool isRetval = ¶mInfo == mMethodInfo->GetRetval(); + + if (paramInfo.IsOut() && !isRetval) { + MOZ_ASSERT(paramIndex < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + Value arg = paramIndex < mArgc ? mArgv[paramIndex] : JS::NullValue(); + if (paramIndex < mArgc) { + RootedObject obj(mCallContext); + if (!arg.isPrimitive()) { + obj = &arg.toObject(); + } + if (!obj || !JS_GetPropertyById(mCallContext, obj, mIdxValueId, srcp)) { + // Explicitly passed in unusable value for out param. Note + // that if i >= mArgc we already know that |arg| is JS::NullValue(), + // and that's ok. + ThrowBadParam(NS_ERROR_XPC_NEED_OUT_OBJECT, paramIndex, mCallContext); + return false; + } + } + } + + return true; +} + +bool CallMethodHelper::GatherAndConvertResults() { + // now we iterate through the native params to gather and convert results + uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + if (!paramInfo.IsOut()) { + continue; + } + + const nsXPTType& type = paramInfo.GetType(); + nsXPTCVariant* dp = GetDispatchParam(i); + RootedValue v(mCallContext, NullValue()); + + uint32_t array_count = 0; + nsID param_iid; + if (!GetInterfaceTypeFromParam(type, ¶m_iid) || + !GetArraySizeFromParam(type, UndefinedHandleValue, &array_count)) + return false; + + nsresult err; + if (!XPCConvert::NativeData2JS(mCallContext, &v, &dp->val, type, ¶m_iid, + array_count, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + + if (¶mInfo == mMethodInfo->GetRetval()) { + mCallContext.SetRetVal(v); + } else if (i < mArgc) { + // we actually assured this before doing the invoke + MOZ_ASSERT(mArgv[i].isObject(), "out var is not object"); + RootedObject obj(mCallContext, &mArgv[i].toObject()); + if (!JS_SetPropertyById(mCallContext, obj, mIdxValueId, v)) { + ThrowBadParam(NS_ERROR_XPC_CANT_SET_OUT_VAL, i, mCallContext); + return false; + } + } else { + MOZ_ASSERT(paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + } + } + + return true; +} + +bool CallMethodHelper::QueryInterfaceFastPath() { + MOZ_ASSERT(mVTableIndex == 0, + "Using the QI fast-path for a method other than QueryInterface"); + + if (mArgc < 1) { + Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); + return false; + } + + if (!mArgv[0].isObject()) { + ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); + return false; + } + + JS::RootedValue iidarg(mCallContext, mArgv[0]); + Maybe<nsID> iid = xpc::JSValue2ID(mCallContext, iidarg); + if (!iid) { + ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); + return false; + } + + nsISupports* qiresult = nullptr; + mInvokeResult = mCallee->QueryInterface(iid.ref(), (void**)&qiresult); + + if (NS_FAILED(mInvokeResult)) { + ThrowBadResult(mInvokeResult, mCallContext); + return false; + } + + RootedValue v(mCallContext, NullValue()); + nsresult err; + bool success = XPCConvert::NativeData2JS(mCallContext, &v, &qiresult, + {nsXPTType::T_INTERFACE_IS}, + iid.ptr(), 0, &err); + NS_IF_RELEASE(qiresult); + + if (!success) { + ThrowBadParam(err, 0, mCallContext); + return false; + } + + mCallContext.SetRetVal(v); + return true; +} + +bool CallMethodHelper::InitializeDispatchParams() { + const uint8_t wantsOptArgc = mMethodInfo->WantsOptArgc() ? 1 : 0; + const uint8_t wantsJSContext = mMethodInfo->WantsContext() ? 1 : 0; + const uint8_t paramCount = mMethodInfo->GetParamCount(); + uint8_t requiredArgs = paramCount; + + // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. + if (mMethodInfo->HasRetval()) { + requiredArgs--; + } + + if (mArgc < requiredArgs || wantsOptArgc) { + if (wantsOptArgc) { + // The implicit JSContext*, if we have one, comes first. + mOptArgcIndex = requiredArgs + wantsJSContext; + } + + // skip over any optional arguments + while (requiredArgs && + mMethodInfo->GetParam(requiredArgs - 1).IsOptional()) { + requiredArgs--; + } + + if (mArgc < requiredArgs) { + Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); + return false; + } + } + + mJSContextIndex = mMethodInfo->IndexOfJSContext(); + + // Allocate enough space in mDispatchParams up-front. + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mDispatchParams.AppendElements(paramCount + wantsJSContext + wantsOptArgc); + + // Initialize each parameter to a valid state (for safe cleanup later). + for (uint8_t i = 0, paramIdx = 0; i < mDispatchParams.Length(); i++) { + nsXPTCVariant& dp = mDispatchParams[i]; + + if (i == mJSContextIndex) { + // Fill in the JSContext argument + dp.type = nsXPTType::T_VOID; + dp.val.p = mCallContext; + } else if (i == mOptArgcIndex) { + // Fill in the optional_argc argument + dp.type = nsXPTType::T_U8; + dp.val.u8 = std::min<uint32_t>(mArgc, paramCount) - requiredArgs; + } else { + // Initialize normal arguments. + const nsXPTParamInfo& param = mMethodInfo->Param(paramIdx); + dp.type = param.Type(); + xpc::InitializeValue(dp.type, &dp.val); + + // Specify the correct storage/calling semantics. This will also set + // the `ptr` field to be self-referential. + if (param.IsIndirect()) { + dp.SetIndirect(); + } + + // Advance to the next normal parameter. + paramIdx++; + } + } + + return true; +} + +bool CallMethodHelper::ConvertIndependentParams(bool* foundDependentParam) { + const uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + + if (paramInfo.GetType().IsDependent()) { + *foundDependentParam = true; + } else if (!ConvertIndependentParam(i)) { + return false; + } + } + + return true; +} + +bool CallMethodHelper::ConvertIndependentParam(uint8_t i) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + const nsXPTType& type = paramInfo.Type(); + nsXPTCVariant* dp = GetDispatchParam(i); + + // Even if there's nothing to convert, we still need to examine the + // JSObject container for out-params. If it's null or otherwise invalid, + // we want to know before the call, rather than after. + // + // This is a no-op for 'in' params. + RootedValue src(mCallContext); + if (!GetOutParamSource(i, &src)) { + return false; + } + + // All that's left to do is value conversion. Bail early if we don't need + // to do that. + if (!paramInfo.IsIn()) { + return true; + } + + // Some types usually don't support default values, but we want to handle + // the default value if IsOptional is true. + if (i >= mArgc) { + MOZ_ASSERT(paramInfo.IsOptional(), "missing non-optional argument!"); + if (type.Tag() == nsXPTType::T_NSID) { + // Use a default value of the null ID for optional NSID objects. + dp->ext.nsid.Clear(); + return true; + } + + if (type.Tag() == nsXPTType::T_ARRAY) { + // Use a default value of empty array for optional Array objects. + dp->ext.array.Clear(); + return true; + } + } + + // We're definitely some variety of 'in' now, so there's something to + // convert. The source value for conversion depends on whether we're + // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, + // so all that's left is 'in'. + if (!paramInfo.IsOut()) { + // Handle the 'in' case. + MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + if (i < mArgc) { + src = mArgv[i]; + } else if (type.Tag() == nsXPTType::T_JSVAL) { + src.setUndefined(); + } else { + src.setNull(); + } + } + + nsID param_iid = {0}; + const nsXPTType& inner = type.InnermostType(); + if (inner.Tag() == nsXPTType::T_INTERFACE) { + if (!inner.GetInterface()) { + return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, i, + mCallContext); + } + param_iid = inner.GetInterface()->IID(); + } + + nsresult err; + if (!XPCConvert::JSData2Native(mCallContext, &dp->val, src, type, ¶m_iid, + 0, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + + return true; +} + +bool CallMethodHelper::ConvertDependentParams() { + const uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + + if (!paramInfo.GetType().IsDependent()) { + continue; + } + if (!ConvertDependentParam(i)) { + return false; + } + } + + return true; +} + +bool CallMethodHelper::ConvertDependentParam(uint8_t i) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + const nsXPTType& type = paramInfo.Type(); + nsXPTCVariant* dp = GetDispatchParam(i); + + // Even if there's nothing to convert, we still need to examine the + // JSObject container for out-params. If it's null or otherwise invalid, + // we want to know before the call, rather than after. + // + // This is a no-op for 'in' params. + RootedValue src(mCallContext); + if (!GetOutParamSource(i, &src)) { + return false; + } + + // All that's left to do is value conversion. Bail early if we don't need + // to do that. + if (!paramInfo.IsIn()) { + return true; + } + + // We're definitely some variety of 'in' now, so there's something to + // convert. The source value for conversion depends on whether we're + // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, + // so all that's left is 'in'. + if (!paramInfo.IsOut()) { + // Handle the 'in' case. + MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + src = i < mArgc ? mArgv[i] : JS::NullValue(); + } + + nsID param_iid; + uint32_t array_count; + if (!GetInterfaceTypeFromParam(type, ¶m_iid) || + !GetArraySizeFromParam(type, src, &array_count)) + return false; + + nsresult err; + + if (!XPCConvert::JSData2Native(mCallContext, &dp->val, src, type, ¶m_iid, + array_count, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + + return true; +} + +nsresult CallMethodHelper::Invoke() { + uint32_t argc = mDispatchParams.Length(); + nsXPTCVariant* argv = mDispatchParams.Elements(); + + return NS_InvokeByIndex(mCallee, mVTableIndex, argc, argv); +} + +static void TraceParam(JSTracer* aTrc, void* aVal, const nsXPTType& aType, + uint32_t aArrayLen = 0) { + if (aType.Tag() == nsXPTType::T_JSVAL) { + JS::TraceRoot(aTrc, (JS::Value*)aVal, "XPCWrappedNative::CallMethod param"); + } else if (aType.Tag() == nsXPTType::T_ARRAY) { + auto* array = (xpt::detail::UntypedTArray*)aVal; + const nsXPTType& elty = aType.ArrayElementType(); + + for (uint32_t i = 0; i < array->Length(); ++i) { + TraceParam(aTrc, elty.ElementPtr(array->Elements(), i), elty); + } + } else if (aType.Tag() == nsXPTType::T_LEGACY_ARRAY && *(void**)aVal) { + const nsXPTType& elty = aType.ArrayElementType(); + + for (uint32_t i = 0; i < aArrayLen; ++i) { + TraceParam(aTrc, elty.ElementPtr(*(void**)aVal, i), elty); + } + } +} + +void CallMethodHelper::trace(JSTracer* aTrc) { + // We need to note each of our initialized parameters which contain jsvals. + for (nsXPTCVariant& param : mDispatchParams) { + // We only need to trace parameters which have an innermost JSVAL. + if (param.type.InnermostType().Tag() != nsXPTType::T_JSVAL) { + continue; + } + + uint32_t arrayLen = 0; + if (!GetArraySizeFromParam(param.type, UndefinedHandleValue, &arrayLen)) { + continue; + } + + TraceParam(aTrc, ¶m.val, param.type, arrayLen); + } +} + +/***************************************************************************/ +// interface methods + +JSObject* XPCWrappedNative::GetJSObject() { return GetFlatJSObject(); } + +XPCWrappedNative* nsIXPConnectWrappedNative::AsXPCWrappedNative() { + return static_cast<XPCWrappedNative*>(this); +} + +nsresult nsIXPConnectWrappedNative::DebugDump(int16_t depth) { + return AsXPCWrappedNative()->DebugDump(depth); +} + +nsresult XPCWrappedNative::DebugDump(int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS( + ("XPCWrappedNative @ %p with mRefCnt = %" PRIuPTR, this, mRefCnt.get())); + XPC_LOG_INDENT(); + + if (HasProto()) { + XPCWrappedNativeProto* proto = GetProto(); + if (depth && proto) { + proto->DebugDump(depth); + } else { + XPC_LOG_ALWAYS(("mMaybeProto @ %p", proto)); + } + } else + XPC_LOG_ALWAYS(("Scope @ %p", GetScope())); + + if (depth && mSet) { + mSet->DebugDump(depth); + } else { + XPC_LOG_ALWAYS(("mSet @ %p", mSet.get())); + } + + XPC_LOG_ALWAYS(("mFlatJSObject of %p", mFlatJSObject.unbarrieredGetPtr())); + XPC_LOG_ALWAYS(("mIdentity of %p", mIdentity.get())); + XPC_LOG_ALWAYS(("mScriptable @ %p", mScriptable.get())); + + if (depth && mScriptable) { + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mFlags of %x", mScriptable->GetScriptableFlags())); + XPC_LOG_ALWAYS(("mJSClass @ %p", mScriptable->GetJSClass())); + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif + return NS_OK; +} + +/***************************************************************************/ + +char* XPCWrappedNative::ToString( + XPCWrappedNativeTearOff* to /* = nullptr */) const { +#ifdef DEBUG +# define FMT_ADDR " @ 0x%p" +# define FMT_STR(str) str +# define PARAM_ADDR(w) , w +#else +# define FMT_ADDR "" +# define FMT_STR(str) +# define PARAM_ADDR(w) +#endif + + UniqueChars sz; + UniqueChars name; + + nsCOMPtr<nsIXPCScriptable> scr = GetScriptable(); + if (scr) { + name = JS_smprintf("%s", scr->GetJSClass()->name); + } + if (to) { + const char* fmt = name ? " (%s)" : "%s"; + name = JS_sprintf_append(std::move(name), fmt, + to->GetInterface()->GetNameString()); + } else if (!name) { + XPCNativeSet* set = GetSet(); + XPCNativeInterface** array = set->GetInterfaceArray(); + uint16_t count = set->GetInterfaceCount(); + MOZ_RELEASE_ASSERT(count >= 1, "Expected at least one interface"); + MOZ_ASSERT(*array[0]->GetIID() == NS_GET_IID(nsISupports), + "The first interface must be nsISupports"); + + // The first interface is always nsISupports, so don't print it, unless + // there are no others. + if (count == 1) { + name = JS_sprintf_append(std::move(name), "nsISupports"); + } else if (count == 2) { + name = + JS_sprintf_append(std::move(name), "%s", array[1]->GetNameString()); + } else { + for (uint16_t i = 1; i < count; i++) { + const char* fmt = (i == 1) ? "(%s" + : (i == count - 1) ? ", %s)" + : ", %s"; + name = + JS_sprintf_append(std::move(name), fmt, array[i]->GetNameString()); + } + } + } + + if (!name) { + return nullptr; + } + const char* fmt = "[xpconnect wrapped %s" FMT_ADDR FMT_STR(" (native") + FMT_ADDR FMT_STR(")") "]"; + if (scr) { + fmt = "[object %s" FMT_ADDR FMT_STR(" (native") FMT_ADDR FMT_STR(")") "]"; + } + sz = + JS_smprintf(fmt, name.get() PARAM_ADDR(this) PARAM_ADDR(mIdentity.get())); + + return sz.release(); + +#undef FMT_ADDR +#undef PARAM_ADDR +} + +/***************************************************************************/ + +#ifdef XPC_CHECK_CLASSINFO_CLAIMS +static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper) { + if (!wrapper || !wrapper->GetClassInfo()) { + return; + } + + nsISupports* obj = wrapper->GetIdentityObject(); + XPCNativeSet* set = wrapper->GetSet(); + uint16_t count = set->GetInterfaceCount(); + for (uint16_t i = 0; i < count; i++) { + nsIClassInfo* clsInfo = wrapper->GetClassInfo(); + XPCNativeInterface* iface = set->GetInterfaceAt(i); + const nsXPTInterfaceInfo* info = iface->GetInterfaceInfo(); + nsISupports* ptr; + + nsresult rv = obj->QueryInterface(info->IID(), (void**)&ptr); + if (NS_SUCCEEDED(rv)) { + NS_RELEASE(ptr); + continue; + } + if (rv == NS_ERROR_OUT_OF_MEMORY) { + continue; + } + + // Houston, We have a problem... + + char* className = nullptr; + char* contractID = nullptr; + const char* interfaceName = info->Name(); + + clsInfo->GetContractID(&contractID); + if (wrapper->GetScriptable()) { + wrapper->GetScriptable()->GetClassName(&className); + } + + printf( + "\n!!! Object's nsIClassInfo lies about its interfaces!!!\n" + " classname: %s \n" + " contractid: %s \n" + " unimplemented interface name: %s\n\n", + className ? className : "<unknown>", + contractID ? contractID : "<unknown>", interfaceName); + + if (className) { + free(className); + } + if (contractID) { + free(contractID); + } + } +} +#endif diff --git a/js/xpconnect/src/XPCWrappedNativeInfo.cpp b/js/xpconnect/src/XPCWrappedNativeInfo.cpp new file mode 100644 index 0000000000..ec20145cad --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeInfo.cpp @@ -0,0 +1,728 @@ +/* -*- 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/. */ + +/* Manage the shared info about interfaces for use by wrappedNatives. */ + +#include "xpcprivate.h" +#include "XPCMaps.h" +#include "js/Wrapper.h" + +#include "mozilla/MemoryReporting.h" +#include "nsIScriptError.h" +#include "nsPrintfCString.h" +#include "nsPointerHashKeys.h" + +using namespace JS; +using namespace mozilla; + +/***************************************************************************/ + +// XPCNativeMember + +// static +bool XPCNativeMember::GetCallInfo(JSObject* funobj, + RefPtr<XPCNativeInterface>* pInterface, + XPCNativeMember** pMember) { + funobj = js::UncheckedUnwrap(funobj); + Value memberVal = + js::GetFunctionNativeReserved(funobj, XPC_FUNCTION_NATIVE_MEMBER_SLOT); + + *pMember = static_cast<XPCNativeMember*>(memberVal.toPrivate()); + *pInterface = (*pMember)->GetInterface(); + + return true; +} + +bool XPCNativeMember::NewFunctionObject(XPCCallContext& ccx, + XPCNativeInterface* iface, + HandleObject parent, Value* pval) { + MOZ_ASSERT(!IsConstant(), + "Only call this if you're sure this is not a constant!"); + + return Resolve(ccx, iface, parent, pval); +} + +bool XPCNativeMember::Resolve(XPCCallContext& ccx, XPCNativeInterface* iface, + HandleObject parent, Value* vp) { + MOZ_ASSERT(iface == GetInterface()); + if (IsConstant()) { + RootedValue resultVal(ccx); + nsCString name; + if (NS_FAILED(iface->GetInterfaceInfo()->GetConstant(mIndex, &resultVal, + getter_Copies(name)))) + return false; + + *vp = resultVal; + + return true; + } + // else... + + // This is a method or attribute - we'll be needing a function object + + int argc; + JSNative callback; + + if (IsMethod()) { + const nsXPTMethodInfo* info; + if (NS_FAILED(iface->GetInterfaceInfo()->GetMethodInfo(mIndex, &info))) { + return false; + } + + // Note: ASSUMES that retval is last arg. + argc = (int)info->ParamCount(); + if (info->HasRetval()) { + argc--; + } + + callback = XPC_WN_CallMethod; + } else { + argc = 0; + callback = XPC_WN_GetterSetter; + } + + jsid name = GetName(); + JS_MarkCrossZoneId(ccx, name); + + JSFunction* fun; + if (name.isString()) { + fun = js::NewFunctionByIdWithReserved(ccx, callback, argc, 0, name); + } else { + fun = js::NewFunctionWithReserved(ccx, callback, argc, 0, nullptr); + } + if (!fun) { + return false; + } + + JSObject* funobj = JS_GetFunctionObject(fun); + if (!funobj) { + return false; + } + + js::SetFunctionNativeReserved(funobj, XPC_FUNCTION_NATIVE_MEMBER_SLOT, + PrivateValue(this)); + js::SetFunctionNativeReserved(funobj, XPC_FUNCTION_PARENT_OBJECT_SLOT, + ObjectValue(*parent)); + + vp->setObject(*funobj); + + return true; +} + +/***************************************************************************/ +// XPCNativeInterface + +XPCNativeInterface::~XPCNativeInterface() { + XPCJSRuntime::Get()->GetIID2NativeInterfaceMap()->Remove(this); +} + +// static +already_AddRefed<XPCNativeInterface> XPCNativeInterface::GetNewOrUsed( + JSContext* cx, const nsIID* iid) { + RefPtr<XPCNativeInterface> iface; + XPCJSRuntime* rt = XPCJSRuntime::Get(); + + IID2NativeInterfaceMap* map = rt->GetIID2NativeInterfaceMap(); + if (!map) { + return nullptr; + } + + iface = map->Find(*iid); + + if (iface) { + return iface.forget(); + } + + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByIID(*iid); + if (!info) { + return nullptr; + } + + return NewInstance(cx, map, info); +} + +// static +already_AddRefed<XPCNativeInterface> XPCNativeInterface::GetNewOrUsed( + JSContext* cx, const nsXPTInterfaceInfo* info) { + RefPtr<XPCNativeInterface> iface; + + XPCJSRuntime* rt = XPCJSRuntime::Get(); + + IID2NativeInterfaceMap* map = rt->GetIID2NativeInterfaceMap(); + if (!map) { + return nullptr; + } + + iface = map->Find(info->IID()); + + if (iface) { + return iface.forget(); + } + + return NewInstance(cx, map, info); +} + +// static +already_AddRefed<XPCNativeInterface> XPCNativeInterface::GetNewOrUsed( + JSContext* cx, const char* name) { + const nsXPTInterfaceInfo* info = nsXPTInterfaceInfo::ByName(name); + return info ? GetNewOrUsed(cx, info) : nullptr; +} + +// static +already_AddRefed<XPCNativeInterface> XPCNativeInterface::GetISupports( + JSContext* cx) { + // XXX We should optimize this to cache this common XPCNativeInterface. + return GetNewOrUsed(cx, &NS_GET_IID(nsISupports)); +} + +// static +already_AddRefed<XPCNativeInterface> XPCNativeInterface::NewInstance( + JSContext* cx, IID2NativeInterfaceMap* aMap, + const nsXPTInterfaceInfo* aInfo) { + // XXX Investigate lazy init? This is a problem given the + // 'placement new' scheme - we need to at least know how big to make + // the object. We might do a scan of methods to determine needed size, + // then make our object, but avoid init'ing *any* members until asked? + // Find out how often we create these objects w/o really looking at + // (or using) the members. + + if (aInfo->IsMainProcessScriptableOnly() && !XRE_IsParentProcess()) { + nsCOMPtr<nsIConsoleService> console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (console) { + const char* intfNameChars = aInfo->Name(); + nsPrintfCString errorMsg("Use of %s in content process is deprecated.", + intfNameChars); + + nsAutoString filename; + uint32_t lineno = 0, column = 0; + nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column); + nsCOMPtr<nsIScriptError> error( + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(NS_ConvertUTF8toUTF16(errorMsg), filename, u""_ns, lineno, + column, nsIScriptError::warningFlag, "chrome javascript"_ns, + false /* from private window */, + true /* from chrome context */); + console->LogMessage(error); + } + } + + // Make sure the code below does not GC. This means we don't need to trace the + // PropertyKeys in the MemberVector, or the XPCNativeInterface we create + // before it's added to the map. + JS::AutoCheckCannotGC nogc; + + const uint16_t methodCount = aInfo->MethodCount(); + const uint16_t constCount = aInfo->ConstantCount(); + const uint16_t totalCount = methodCount + constCount; + + using MemberVector = + mozilla::Vector<XPCNativeMember, 16, InfallibleAllocPolicy>; + MemberVector members; + MOZ_ALWAYS_TRUE(members.reserve(totalCount)); + + // NOTE: since getters and setters share a member, we might not use all + // of the member objects. + + for (unsigned int i = 0; i < methodCount; i++) { + const nsXPTMethodInfo& info = aInfo->Method(i); + + // don't reflect Addref or Release + if (i == 1 || i == 2) { + continue; + } + + if (!info.IsReflectable()) { + continue; + } + + jsid name; + if (!info.GetId(cx, name)) { + NS_ERROR("bad method name"); + return nullptr; + } + + if (info.IsSetter()) { + MOZ_ASSERT(!members.empty(), "bad setter"); + // Note: ASSUMES Getter/Setter pairs are next to each other + // This is a rule of the typelib spec. + XPCNativeMember* cur = &members.back(); + MOZ_ASSERT(cur->GetName() == name, "bad setter"); + MOZ_ASSERT(cur->IsReadOnlyAttribute(), "bad setter"); + MOZ_ASSERT(cur->GetIndex() == i - 1, "bad setter"); + cur->SetWritableAttribute(); + } else { + // XXX need better way to find dups + // MOZ_ASSERT(!LookupMemberByID(name),"duplicate method name"); + size_t indexInInterface = members.length(); + if (indexInInterface == XPCNativeMember::GetMaxIndexInInterface()) { + NS_WARNING("Too many members in interface"); + return nullptr; + } + XPCNativeMember cur; + cur.SetName(name); + if (info.IsGetter()) { + cur.SetReadOnlyAttribute(i); + } else { + cur.SetMethod(i); + } + cur.SetIndexInInterface(indexInInterface); + members.infallibleAppend(cur); + } + } + + for (unsigned int i = 0; i < constCount; i++) { + RootedValue constant(cx); + nsCString namestr; + if (NS_FAILED(aInfo->GetConstant(i, &constant, getter_Copies(namestr)))) { + return nullptr; + } + + RootedString str(cx, JS_AtomizeString(cx, namestr.get())); + if (!str) { + NS_ERROR("bad constant name"); + return nullptr; + } + jsid name = PropertyKey::NonIntAtom(str); + + // XXX need better way to find dups + // MOZ_ASSERT(!LookupMemberByID(name),"duplicate method/constant name"); + size_t indexInInterface = members.length(); + if (indexInInterface == XPCNativeMember::GetMaxIndexInInterface()) { + NS_WARNING("Too many members in interface"); + return nullptr; + } + XPCNativeMember cur; + cur.SetName(name); + cur.SetConstant(i); + cur.SetIndexInInterface(indexInInterface); + members.infallibleAppend(cur); + } + + const char* bytes = aInfo->Name(); + if (!bytes) { + return nullptr; + } + RootedString str(cx, JS_AtomizeString(cx, bytes)); + if (!str) { + return nullptr; + } + + RootedId interfaceName(cx, PropertyKey::NonIntAtom(str)); + + // Use placement new to create an object with the right amount of space + // to hold the members array + size_t size = sizeof(XPCNativeInterface); + if (members.length() > 1) { + size += (members.length() - 1) * sizeof(XPCNativeMember); + } + void* place = new char[size]; + if (!place) { + return nullptr; + } + + RefPtr<XPCNativeInterface> obj = + new (place) XPCNativeInterface(aInfo, interfaceName); + + obj->mMemberCount = members.length(); + // copy valid members + if (!members.empty()) { + memcpy(obj->mMembers, members.begin(), + members.length() * sizeof(XPCNativeMember)); + } + + if (!aMap->AddNew(obj)) { + NS_ERROR("failed to add our interface!"); + return nullptr; + } + + return obj.forget(); +} + +// static +void XPCNativeInterface::DestroyInstance(XPCNativeInterface* inst) { + inst->~XPCNativeInterface(); + delete[] (char*)inst; +} + +size_t XPCNativeInterface::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this); +} + +void XPCNativeInterface::Trace(JSTracer* trc) { + JS::TraceRoot(trc, &mName, "XPCNativeInterface::mName"); + + for (size_t i = 0; i < mMemberCount; i++) { + JS::PropertyKey key = mMembers[i].GetName(); + JS::TraceRoot(trc, &key, "XPCNativeInterface::mMembers"); + MOZ_ASSERT(mMembers[i].GetName() == key); + } +} + +void IID2NativeInterfaceMap::Trace(JSTracer* trc) { + for (Map::Enum e(mMap); !e.empty(); e.popFront()) { + XPCNativeInterface* iface = e.front().value(); + iface->Trace(trc); + } +} + +void XPCNativeInterface::DebugDump(int16_t depth) { +#ifdef DEBUG + XPC_LOG_ALWAYS(("XPCNativeInterface @ %p", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("name is %s", GetNameString())); + XPC_LOG_ALWAYS(("mInfo @ %p", mInfo)); + XPC_LOG_OUTDENT(); +#endif +} + +/***************************************************************************/ +// XPCNativeSetKey + +HashNumber XPCNativeSetKey::Hash() const { + HashNumber h = 0; + + if (mBaseSet) { + // If we ever start using mCx here, adjust the constructors accordingly. + XPCNativeInterface** current = mBaseSet->GetInterfaceArray(); + uint16_t count = mBaseSet->GetInterfaceCount(); + for (uint16_t i = 0; i < count; i++) { + h = AddToHash(h, *(current++)); + } + } else { + // A newly created set will contain nsISupports first... + RefPtr<XPCNativeInterface> isupp = XPCNativeInterface::GetISupports(mCx); + h = AddToHash(h, isupp.get()); + + // ...but no more than once. + if (isupp == mAddition) { + return h; + } + } + + if (mAddition) { + h = AddToHash(h, mAddition.get()); + } + + return h; +} + +/***************************************************************************/ +// XPCNativeSet + +XPCNativeSet::~XPCNativeSet() { + // Remove |this| before we clear the interfaces to ensure that the + // hashtable look up is correct. + + XPCJSRuntime::Get()->GetNativeSetMap()->Remove(this); + + for (int i = 0; i < mInterfaceCount; i++) { + NS_RELEASE(mInterfaces[i]); + } +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::GetNewOrUsed(JSContext* cx, + const nsIID* iid) { + RefPtr<XPCNativeInterface> iface = XPCNativeInterface::GetNewOrUsed(cx, iid); + if (!iface) { + return nullptr; + } + + XPCNativeSetKey key(cx, iface); + + XPCJSRuntime* xpcrt = XPCJSRuntime::Get(); + NativeSetMap* map = xpcrt->GetNativeSetMap(); + if (!map) { + return nullptr; + } + + RefPtr<XPCNativeSet> set = map->Find(&key); + + if (set) { + return set.forget(); + } + + set = NewInstance(cx, {std::move(iface)}); + if (!set) { + return nullptr; + } + + if (!map->AddNew(&key, set)) { + NS_ERROR("failed to add our set!"); + set = nullptr; + } + + return set.forget(); +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::GetNewOrUsed( + JSContext* cx, nsIClassInfo* classInfo) { + XPCJSRuntime* xpcrt = XPCJSRuntime::Get(); + ClassInfo2NativeSetMap* map = xpcrt->GetClassInfo2NativeSetMap(); + if (!map) { + return nullptr; + } + + RefPtr<XPCNativeSet> set = map->Find(classInfo); + + if (set) { + return set.forget(); + } + + AutoTArray<nsIID, 4> iids; + if (NS_FAILED(classInfo->GetInterfaces(iids))) { + // Note: I'm making it OK for this call to fail so that one can add + // nsIClassInfo to classes implemented in script without requiring this + // method to be implemented. + + // Make sure these are set correctly... + iids.Clear(); + } + + // Try to look up each IID's XPCNativeInterface object. + nsTArray<RefPtr<XPCNativeInterface>> interfaces(iids.Length()); + for (auto& iid : iids) { + RefPtr<XPCNativeInterface> iface = + XPCNativeInterface::GetNewOrUsed(cx, &iid); + if (iface) { + interfaces.AppendElement(iface.forget()); + } + } + + // Build a set from the interfaces specified here. + if (interfaces.Length() > 0) { + set = NewInstance(cx, std::move(interfaces)); + if (set) { + NativeSetMap* map2 = xpcrt->GetNativeSetMap(); + if (!map2) { + return set.forget(); + } + + XPCNativeSetKey key(set); + XPCNativeSet* set2 = map2->Add(&key, set); + if (!set2) { + NS_ERROR("failed to add our set"); + return nullptr; + } + + // It is okay to find an existing entry here because + // we did not look for one before we called Add(). + if (set2 != set) { + set = set2; + } + } + } else { + set = GetNewOrUsed(cx, &NS_GET_IID(nsISupports)); + } + + if (set) { +#ifdef DEBUG + XPCNativeSet* set2 = +#endif + map->Add(classInfo, set); + MOZ_ASSERT(set2, "failed to add our set!"); + MOZ_ASSERT(set2 == set, "hashtables inconsistent!"); + } + + return set.forget(); +} + +// static +void XPCNativeSet::ClearCacheEntryForClassInfo(nsIClassInfo* classInfo) { + XPCJSRuntime* xpcrt = nsXPConnect::GetRuntimeInstance(); + ClassInfo2NativeSetMap* map = xpcrt->GetClassInfo2NativeSetMap(); + if (map) { + map->Remove(classInfo); + } +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::GetNewOrUsed( + JSContext* cx, XPCNativeSetKey* key) { + NativeSetMap* map = XPCJSRuntime::Get()->GetNativeSetMap(); + if (!map) { + return nullptr; + } + + RefPtr<XPCNativeSet> set = map->Find(key); + + if (set) { + return set.forget(); + } + + if (key->GetBaseSet()) { + set = NewInstanceMutate(key); + } else { + set = NewInstance(cx, {key->GetAddition()}); + } + + if (!set) { + return nullptr; + } + + if (!map->AddNew(key, set)) { + NS_ERROR("failed to add our set!"); + set = nullptr; + } + + return set.forget(); +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::GetNewOrUsed( + JSContext* cx, XPCNativeSet* firstSet, XPCNativeSet* secondSet, + bool preserveFirstSetOrder) { + // Figure out how many interfaces we'll need in the new set. + uint32_t uniqueCount = firstSet->mInterfaceCount; + for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) { + if (!firstSet->HasInterface(secondSet->mInterfaces[i])) { + uniqueCount++; + } + } + + // If everything in secondSet was a duplicate, we can just use the first + // set. + if (uniqueCount == firstSet->mInterfaceCount) { + return RefPtr<XPCNativeSet>(firstSet).forget(); + } + + // If the secondSet is just a superset of the first, we can use it provided + // that the caller doesn't care about ordering. + if (!preserveFirstSetOrder && uniqueCount == secondSet->mInterfaceCount) { + return RefPtr<XPCNativeSet>(secondSet).forget(); + } + + // Ok, darn. Now we have to make a new set. + // + // It would be faster to just create the new set all at once, but that + // would involve wrangling with some pretty hairy code - especially since + // a lot of stuff assumes that sets are created by adding one interface to an + // existing set. So let's just do the slow and easy thing and hope that the + // above optimizations handle the common cases. + RefPtr<XPCNativeSet> currentSet = firstSet; + for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) { + XPCNativeInterface* iface = secondSet->mInterfaces[i]; + if (!currentSet->HasInterface(iface)) { + // Create a new augmented set, inserting this interface at the end. + XPCNativeSetKey key(currentSet, iface); + currentSet = XPCNativeSet::GetNewOrUsed(cx, &key); + if (!currentSet) { + return nullptr; + } + } + } + + // We've got the union set. Hand it back to the caller. + MOZ_ASSERT(currentSet->mInterfaceCount == uniqueCount); + return currentSet.forget(); +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::NewInstance( + JSContext* cx, nsTArray<RefPtr<XPCNativeInterface>>&& array) { + if (array.Length() == 0) { + return nullptr; + } + + // We impose the invariant: + // "All sets have exactly one nsISupports interface and it comes first." + // This is the place where we impose that rule - even if given inputs + // that don't exactly follow the rule. + + RefPtr<XPCNativeInterface> isup = XPCNativeInterface::GetISupports(cx); + uint16_t slots = array.Length() + 1; + + for (auto key = array.begin(); key != array.end(); key++) { + if (*key == isup) { + slots--; + } + } + + // Use placement new to create an object with the right amount of space + // to hold the members array + int size = sizeof(XPCNativeSet); + if (slots > 1) { + size += (slots - 1) * sizeof(XPCNativeInterface*); + } + void* place = new char[size]; + RefPtr<XPCNativeSet> obj = new (place) XPCNativeSet(); + + // Stick the nsISupports in front and skip additional nsISupport(s) + XPCNativeInterface** outp = (XPCNativeInterface**)&obj->mInterfaces; + + NS_ADDREF(*(outp++) = isup); + + for (auto key = array.begin(); key != array.end(); key++) { + RefPtr<XPCNativeInterface> cur = std::move(*key); + if (isup == cur) { + continue; + } + *(outp++) = cur.forget().take(); + } + obj->mInterfaceCount = slots; + + return obj.forget(); +} + +// static +already_AddRefed<XPCNativeSet> XPCNativeSet::NewInstanceMutate( + XPCNativeSetKey* key) { + XPCNativeSet* otherSet = key->GetBaseSet(); + XPCNativeInterface* newInterface = key->GetAddition(); + + MOZ_ASSERT(otherSet); + + if (!newInterface) { + return nullptr; + } + + // Use placement new to create an object with the right amount of space + // to hold the members array + int size = sizeof(XPCNativeSet); + size += otherSet->mInterfaceCount * sizeof(XPCNativeInterface*); + void* place = new char[size]; + RefPtr<XPCNativeSet> obj = new (place) XPCNativeSet(); + + obj->mInterfaceCount = otherSet->mInterfaceCount + 1; + + XPCNativeInterface** src = otherSet->mInterfaces; + XPCNativeInterface** dest = obj->mInterfaces; + for (uint16_t i = 0; i < otherSet->mInterfaceCount; i++) { + NS_ADDREF(*dest++ = *src++); + } + NS_ADDREF(*dest++ = newInterface); + + return obj.forget(); +} + +// static +void XPCNativeSet::DestroyInstance(XPCNativeSet* inst) { + inst->~XPCNativeSet(); + delete[] (char*)inst; +} + +size_t XPCNativeSet::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this); +} + +void XPCNativeSet::DebugDump(int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCNativeSet @ %p", this)); + XPC_LOG_INDENT(); + + XPC_LOG_ALWAYS(("mInterfaceCount of %d", mInterfaceCount)); + if (depth) { + for (uint16_t i = 0; i < mInterfaceCount; i++) { + mInterfaces[i]->DebugDump(depth); + } + } + XPC_LOG_OUTDENT(); +#endif +} diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp new file mode 100644 index 0000000000..6de0e959fd --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -0,0 +1,1236 @@ +/* -*- 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/. */ + +/* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "js/CharacterEncoding.h" +#include "js/Class.h" +#include "js/Object.h" // JS::GetClass +#include "js/Printf.h" +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_GetProperty, JS_GetPropertyById +#include "js/Symbol.h" + +#include <string_view> + +using namespace mozilla; +using namespace JS; +using namespace xpc; + +/***************************************************************************/ + +// All of the exceptions thrown into JS from this file go through here. +// That makes this a nice place to set a breakpoint. + +static bool Throw(nsresult errNum, JSContext* cx) { + XPCThrower::Throw(errNum, cx); + return false; +} + +// Handy macro used in many callback stub below. + +#define THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper) \ + PR_BEGIN_MACRO \ + if (!wrapper) return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ + if (!wrapper->IsValid()) return Throw(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, cx); \ + PR_END_MACRO + +/***************************************************************************/ + +static bool ToStringGuts(XPCCallContext& ccx) { + UniqueChars sz; + XPCWrappedNative* wrapper = ccx.GetWrapper(); + + if (wrapper) { + sz.reset(wrapper->ToString(ccx.GetTearOff())); + } else { + sz = JS_smprintf("[xpconnect wrapped native prototype]"); + } + + if (!sz) { + JS_ReportOutOfMemory(ccx); + return false; + } + + JSString* str = JS_NewStringCopyZ(ccx, sz.get()); + if (!str) { + return false; + } + + ccx.SetRetVal(JS::StringValue(str)); + return true; +} + +/***************************************************************************/ + +static bool XPC_WN_Shared_ToString(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx); + if (!args.computeThis(cx, &obj)) { + return false; + } + + XPCCallContext ccx(cx, obj); + if (!ccx.IsValid()) { + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + } + ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); + ccx.SetArgsAndResultPtr(args.length(), args.array(), vp); + return ToStringGuts(ccx); +} + +static bool XPC_WN_Shared_ToSource(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + static constexpr std::string_view empty = "({})"; + JSString* str = JS_NewStringCopyN(cx, empty.data(), empty.length()); + if (!str) { + return false; + } + args.rval().setString(str); + + return true; +} + +static bool XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx); + if (!JS_ValueToObject(cx, args.thisv(), &obj)) { + return false; + } + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + JSType hint; + if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) { + return false; + } + + if (hint == JSTYPE_NUMBER) { + args.rval().set(NaNValue()); + return true; + } + + MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_UNDEFINED); + ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); + ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address()); + + XPCNativeMember* member = ccx.GetMember(); + if (member && member->IsMethod()) { + if (!XPCWrappedNative::CallMethod(ccx)) { + return false; + } + + if (args.rval().isPrimitive()) { + return true; + } + } + + // else... + return ToStringGuts(ccx); +} + +/***************************************************************************/ + +// A "double wrapped object" is a user JSObject that has been wrapped as a +// wrappedJS in order to be used by native code and then re-wrapped by a +// wrappedNative wrapper to be used by JS code. One might think of it as: +// wrappedNative(wrappedJS(underlying_JSObject)) +// This is done (as opposed to just unwrapping the wrapped JS and automatically +// returning the underlying JSObject) so that JS callers will see what looks +// Like any other xpcom object - and be limited to use its interfaces. +// + +/** + * When JavaScript code uses a component that is itself implemented in + * JavaScript then XPConnect will build a wrapper rather than directly + * expose the JSObject of the component. This allows components implemented + * in JavaScript to 'look' just like any other xpcom component (from the + * perspective of the JavaScript caller). This insulates the component from + * the caller and hides any properties or methods that are not part of the + * interface as declared in xpidl. Usually this is a good thing. + * + * However, in some cases it is useful to allow the JS caller access to the + * JS component's underlying implementation. In order to facilitate this + * XPConnect supports the 'wrappedJSObject' property. This 'wrappedJSObject' + * property is different than the XrayWrapper meaning. (The naming collision + * avoids having more than one magic XPConnect property name, but is + * confusing.) + * + * The caller code can do: + * + * // 'foo' is some xpcom component (that might be implemented in JS). + * var bar = foo.wrappedJSObject; + * if(bar) { + * // bar is the underlying JSObject. Do stuff with it here. + * } + * + * Recall that 'foo' above is an XPConnect wrapper, not the underlying JS + * object. The property get "foo.wrappedJSObject" will only succeed if three + * conditions are met: + * + * 1) 'foo' really is an XPConnect wrapper around a JSObject. + * 3) The caller must be system JS and not content. Double-wrapped XPCWJS should + * not be exposed to content except with a remote-XUL domain. + * + * Notes: + * + * a) If 'foo' above were the underlying JSObject and not a wrapper at all, + * then this all just works and XPConnect is not part of the picture at all. + * b) One might ask why 'foo' should not just implement an interface through + * which callers might get at the underlying object. There are two reasons: + * i) XPConnect would still have to do magic since JSObject is not a + * scriptable type. + * ii) Avoiding the explicit interface makes it easier for both the caller + * and the component. + */ + +static JSObject* GetDoubleWrappedJSObject(XPCCallContext& ccx, + XPCWrappedNative* wrapper) { + RootedObject obj(ccx); + { + nsCOMPtr<nsIXPConnectWrappedJS> underware = + do_QueryInterface(wrapper->GetIdentityObject()); + if (!underware) { + return nullptr; + } + RootedObject mainObj(ccx, underware->GetJSObject()); + if (mainObj) { + JSAutoRealm ar(ccx, underware->GetJSObjectGlobal()); + + // We don't have to root this ID, as it's already rooted by our context. + HandleId id = + ccx.GetContext()->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT); + + // If the `wrappedJSObject` property is defined, use the result of getting + // that property, otherwise fall back to the `mainObj` object which is + // directly being wrapped. + RootedValue val(ccx); + if (JS_GetPropertyById(ccx, mainObj, id, &val) && !val.isPrimitive()) { + obj = val.toObjectOrNull(); + } else { + obj = mainObj; + } + } + } + return obj; +} + +// This is the getter native function we use to handle 'wrappedJSObject' for +// double wrapped JSObjects. + +static bool XPC_WN_DoubleWrappedGetter(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject()) { + JS_ReportErrorASCII( + cx, + "xpconnect double wrapped getter called on incompatible non-object"); + return false; + } + RootedObject obj(cx, &args.thisv().toObject()); + + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, + "bad function"); + + RootedObject realObject(cx, GetDoubleWrappedJSObject(ccx, wrapper)); + if (!realObject) { + // This is pretty unexpected at this point. The object originally + // responded to this get property call and now gives no object. + // XXX Should this throw something at the caller? + args.rval().setNull(); + return true; + } + + // It is a double wrapped object. This should really never appear in + // content these days, but addons still do it - see bug 965921. + if (MOZ_UNLIKELY(!nsContentUtils::IsSystemCaller(cx))) { + JS_ReportErrorASCII(cx, + "Attempt to use .wrappedJSObject in untrusted code"); + return false; + } + args.rval().setObject(*realObject); + return JS_WrapValue(cx, args.rval()); +} + +/***************************************************************************/ + +// This is our shared function to define properties on our JSObjects. + +/* + * NOTE: + * We *never* set the tearoff names (e.g. nsIFoo) as JS_ENUMERATE. + * We *never* set toString or toSource as JS_ENUMERATE. + */ + +static bool DefinePropertyIfFound( + XPCCallContext& ccx, HandleObject obj, HandleId idArg, XPCNativeSet* set, + XPCNativeInterface* ifaceArg, XPCNativeMember* member, + XPCWrappedNativeScope* scope, bool reflectToStringAndToSource, + XPCWrappedNative* wrapperToReflectInterfaceNames, + XPCWrappedNative* wrapperToReflectDoubleWrap, nsIXPCScriptable* scr, + unsigned propFlags, bool* resolved) { + RootedId id(ccx, idArg); + RefPtr<XPCNativeInterface> iface = ifaceArg; + XPCJSContext* xpccx = ccx.GetContext(); + bool found; + const char* name; + + propFlags |= JSPROP_RESOLVING; + + if (set) { + if (iface) { + found = true; + } else { + found = set->FindMember(id, &member, &iface); + } + } else + found = (nullptr != (member = iface->FindMember(id))); + + if (!found) { + if (reflectToStringAndToSource) { + JSNative call; + if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING)) { + call = XPC_WN_Shared_ToString; + name = xpccx->GetStringName(XPCJSContext::IDX_TO_STRING); + } else if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE)) { + call = XPC_WN_Shared_ToSource; + name = xpccx->GetStringName(XPCJSContext::IDX_TO_SOURCE); + } else if (id.isWellKnownSymbol(JS::SymbolCode::toPrimitive)) { + call = XPC_WN_Shared_toPrimitive; + name = "[Symbol.toPrimitive]"; + } else { + call = nullptr; + } + + if (call) { + RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name)); + if (!fun) { + JS_ReportOutOfMemory(ccx); + return false; + } + + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + RootedObject value(ccx, JS_GetFunctionObject(fun)); + return JS_DefinePropertyById(ccx, obj, id, value, + propFlags & ~JSPROP_ENUMERATE); + } + } + // This *might* be a tearoff name that is not yet part of our + // set. Let's lookup the name and see if it is the name of an + // interface. Then we'll see if the object actually *does* this + // interface and add a tearoff as necessary. + + if (wrapperToReflectInterfaceNames) { + JS::UniqueChars name; + RefPtr<XPCNativeInterface> iface2; + XPCWrappedNativeTearOff* to; + RootedObject jso(ccx); + nsresult rv = NS_OK; + + bool defineProperty = false; + do { + if (!id.isString()) { + break; + } + + name = JS_EncodeStringToLatin1(ccx, id.toString()); + if (!name) { + break; + } + + iface2 = XPCNativeInterface::GetNewOrUsed(ccx, name.get()); + if (!iface2) { + break; + } + + to = + wrapperToReflectInterfaceNames->FindTearOff(ccx, iface2, true, &rv); + if (!to) { + break; + } + + jso = to->GetJSObject(); + if (!jso) { + break; + } + + defineProperty = true; + } while (false); + + if (defineProperty) { + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + return JS_DefinePropertyById(ccx, obj, id, jso, + propFlags & ~JSPROP_ENUMERATE); + } else if (NS_FAILED(rv) && rv != NS_ERROR_NO_INTERFACE) { + return Throw(rv, ccx); + } + } + + // This *might* be a double wrapped JSObject + if (wrapperToReflectDoubleWrap && + id == xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT) && + GetDoubleWrappedJSObject(ccx, wrapperToReflectDoubleWrap)) { + // We build and add a getter function. + // A security check is done on a per-get basis. + + JSFunction* fun; + + id = xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT); + name = xpccx->GetStringName(XPCJSContext::IDX_WRAPPED_JSOBJECT); + + fun = JS_NewFunction(ccx, XPC_WN_DoubleWrappedGetter, 0, 0, name); + + if (!fun) { + return false; + } + + RootedObject funobj(ccx, JS_GetFunctionObject(fun)); + if (!funobj) { + return false; + } + + propFlags &= ~JSPROP_ENUMERATE; + + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + return JS_DefinePropertyById(ccx, obj, id, funobj, nullptr, propFlags); + } + + if (resolved) { + *resolved = false; + } + return true; + } + + if (!member) { + if (wrapperToReflectInterfaceNames) { + XPCWrappedNativeTearOff* to = + wrapperToReflectInterfaceNames->FindTearOff(ccx, iface, true); + + if (!to) { + return false; + } + RootedObject jso(ccx, to->GetJSObject()); + if (!jso) { + return false; + } + + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + return JS_DefinePropertyById(ccx, obj, id, jso, + propFlags & ~JSPROP_ENUMERATE); + } + if (resolved) { + *resolved = false; + } + return true; + } + + if (member->IsConstant()) { + RootedValue val(ccx); + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + return member->GetConstantValue(ccx, iface, val.address()) && + JS_DefinePropertyById(ccx, obj, id, val, propFlags); + } + + if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING) || + id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE) || + (scr && scr->DontEnumQueryInterface() && + id == xpccx->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE))) + propFlags &= ~JSPROP_ENUMERATE; + + RootedValue funval(ccx); + if (!member->NewFunctionObject(ccx, iface, obj, funval.address())) { + return false; + } + + if (member->IsMethod()) { + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + return JS_DefinePropertyById(ccx, obj, id, funval, propFlags); + } + + // else... + + MOZ_ASSERT(member->IsAttribute(), "way broken!"); + + propFlags &= ~JSPROP_READONLY; + RootedObject funobjGetter(ccx, funval.toObjectOrNull()); + RootedObject funobjSetter(ccx); + if (member->IsWritableAttribute()) { + funobjSetter = funobjGetter; + } + + AutoResolveName arn(ccx, id); + if (resolved) { + *resolved = true; + } + + return JS_DefinePropertyById(ccx, obj, id, funobjGetter, funobjSetter, + propFlags); +} + +/***************************************************************************/ +/***************************************************************************/ + +static bool XPC_WN_OnlyIWrite_AddPropertyStub(JSContext* cx, HandleObject obj, + HandleId id, HandleValue v) { + XPCCallContext ccx(cx, obj, nullptr, id); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + // Allow only XPConnect to add/set the property + if (ccx.GetResolveName() == id) { + return true; + } + + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool XPC_WN_CannotModifyPropertyStub(JSContext* cx, HandleObject obj, + HandleId id, HandleValue v) { + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool XPC_WN_CannotDeletePropertyStub(JSContext* cx, HandleObject obj, + HandleId id, ObjectOpResult& result) { + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + // Since we aren't going to enumerate tearoff names and the prototype + // handles non-mutated members, we can do this potential short-circuit. + if (!wrapper->HasMutatedSet()) { + return true; + } + + XPCNativeSet* set = wrapper->GetSet(); + XPCNativeSet* protoSet = + wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr; + + uint16_t interface_count = set->GetInterfaceCount(); + XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); + for (uint16_t i = 0; i < interface_count; i++) { + XPCNativeInterface* iface = interfaceArray[i]; + uint16_t member_count = iface->GetMemberCount(); + for (uint16_t k = 0; k < member_count; k++) { + XPCNativeMember* member = iface->GetMemberAt(k); + jsid name = member->GetName(); + + // Skip if this member is going to come from the proto. + uint16_t index; + if (protoSet && protoSet->FindMember(name, nullptr, &index) && index == i) + continue; + + JS_MarkCrossZoneId(cx, name); + if (!xpc_ForcePropertyResolve(cx, obj, name)) { + return false; + } + } + } + return true; +} + +/***************************************************************************/ + +enum WNHelperType { WN_NOHELPER, WN_HELPER }; + +static void WrappedNativeFinalize(JS::GCContext* gcx, JSObject* obj, + WNHelperType helperType) { + const JSClass* clazz = JS::GetClass(obj); + if (clazz->flags & JSCLASS_DOM_GLOBAL) { + mozilla::dom::DestroyProtoAndIfaceCache(obj); + } + XPCWrappedNative* wrapper = JS::GetObjectISupports<XPCWrappedNative>(obj); + if (!wrapper) { + return; + } + + if (helperType == WN_HELPER) { + wrapper->GetScriptable()->Finalize(wrapper, gcx, obj); + } + wrapper->FlatJSObjectFinalized(); +} + +static size_t WrappedNativeObjectMoved(JSObject* obj, JSObject* old) { + XPCWrappedNative* wrapper = JS::GetObjectISupports<XPCWrappedNative>(obj); + if (!wrapper) { + return 0; + } + + wrapper->FlatJSObjectMoved(obj, old); + return 0; +} + +void XPC_WN_NoHelper_Finalize(JS::GCContext* gcx, JSObject* obj) { + WrappedNativeFinalize(gcx, obj, WN_NOHELPER); +} + +/* + * General comment about XPConnect tracing: Given a C++ object |wrapper| and its + * corresponding JS object |obj|, calling |wrapper->TraceSelf| will ask the JS + * engine to mark |obj|. Eventually, this will lead to the trace hook being + * called for |obj|. The trace hook should call |wrapper->TraceInside|, which + * should mark any JS objects held by |wrapper| as members. + */ + +/* static */ +void XPCWrappedNative::Trace(JSTracer* trc, JSObject* obj) { + const JSClass* clazz = JS::GetClass(obj); + if (clazz->flags & JSCLASS_DOM_GLOBAL) { + mozilla::dom::TraceProtoAndIfaceCache(trc, obj); + } + MOZ_ASSERT(clazz->isWrappedNative()); + + XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj); + if (wrapper && wrapper->IsValid()) { + wrapper->TraceInside(trc); + } +} + +void XPCWrappedNative_Trace(JSTracer* trc, JSObject* obj) { + XPCWrappedNative::Trace(trc, obj); +} + +static bool XPC_WN_NoHelper_Resolve(JSContext* cx, HandleObject obj, + HandleId id, bool* resolvedp) { + XPCCallContext ccx(cx, obj, nullptr, id); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCNativeSet* set = ccx.GetSet(); + if (!set) { + return true; + } + + // Don't resolve properties that are on our prototype. + if (ccx.GetInterface() && !ccx.GetStaticMemberIsLocal()) { + return true; + } + + return DefinePropertyIfFound( + ccx, obj, id, set, nullptr, nullptr, wrapper->GetScope(), true, wrapper, + wrapper, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT, + resolvedp); +} + +static const JSClassOps XPC_WN_NoHelper_JSClassOps = { + XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty + XPC_WN_CannotDeletePropertyStub, // delProperty + XPC_WN_Shared_Enumerate, // enumerate + nullptr, // newEnumerate + XPC_WN_NoHelper_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_NoHelper_Finalize, // finalize + nullptr, // call + nullptr, // construct + XPCWrappedNative::Trace, // trace +}; + +const js::ClassExtension XPC_WN_JSClassExtension = { + WrappedNativeObjectMoved, // objectMovedOp +}; + +const JSClass XPC_WN_NoHelper_JSClass = { + "XPCWrappedNative_NoHelper", + JSCLASS_IS_WRAPPED_NATIVE | JSCLASS_HAS_RESERVED_SLOTS(1) | + JSCLASS_SLOT0_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE, + &XPC_WN_NoHelper_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_JSClassExtension, + JS_NULL_OBJECT_OPS}; + +/***************************************************************************/ + +bool XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, + HandleId id, HandleValue v) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + if (ccx.GetResolvingWrapper() == wrapper) { + return true; + } + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, HandleObject obj, + HandleId id, + ObjectOpResult& result) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + if (ccx.GetResolvingWrapper() == wrapper) { + return result.succeed(); + } + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +// macro fun! +#define PRE_HELPER_STUB \ + /* It's very important for "unwrapped" to be rooted here. */ \ + RootedObject unwrapped(cx, js::CheckedUnwrapDynamic(obj, cx, false)); \ + if (!unwrapped) { \ + JS_ReportErrorASCII(cx, "Permission denied to operate on object."); \ + return false; \ + } \ + if (!IsWrappedNativeReflector(unwrapped)) { \ + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ + } \ + XPCWrappedNative* wrapper = XPCWrappedNative::Get(unwrapped); \ + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); \ + bool retval = true; \ + nsresult rv = wrapper->GetScriptable()-> + +#define POST_HELPER_STUB \ + if (NS_FAILED(rv)) return Throw(rv, cx); \ + return retval; + +bool XPC_WN_Helper_Call(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + // N.B. we want obj to be the callee, not JS_THIS(cx, vp) + RootedObject obj(cx, &args.callee()); + + XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) { + return false; + } + + PRE_HELPER_STUB + Call(wrapper, cx, obj, args, &retval); + POST_HELPER_STUB +} + +bool XPC_WN_Helper_Construct(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + RootedObject obj(cx, &args.callee()); + if (!obj) { + return false; + } + + XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) { + return false; + } + + PRE_HELPER_STUB + Construct(wrapper, cx, obj, args, &retval); + POST_HELPER_STUB +} + +static bool XPC_WN_Helper_HasInstance(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "WrappedNative[Symbol.hasInstance]", 1)) { + return false; + } + + if (!args.thisv().isObject()) { + JS_ReportErrorASCII( + cx, "WrappedNative[Symbol.hasInstance]: unexpected this value"); + return false; + } + + RootedObject obj(cx, &args.thisv().toObject()); + RootedValue val(cx, args.get(0)); + + bool retval2; + PRE_HELPER_STUB + HasInstance(wrapper, cx, obj, val, &retval2, &retval); + args.rval().setBoolean(retval2); + POST_HELPER_STUB +} + +void XPC_WN_Helper_Finalize(JS::GCContext* gcx, JSObject* obj) { + WrappedNativeFinalize(gcx, obj, WN_HELPER); +} + +// RAII class used to store the wrapper in the context when resolving a lazy +// property on its JS reflector. This is used by XPC_WN_MaybeResolving to allow +// adding properties while resolving. +class MOZ_RAII AutoSetResolvingWrapper { + public: + AutoSetResolvingWrapper(XPCCallContext& ccx, XPCWrappedNative* wrapper) + : mCcx(ccx), mOldResolvingWrapper(ccx.SetResolvingWrapper(wrapper)) {} + + ~AutoSetResolvingWrapper() { + (void)mCcx.SetResolvingWrapper(mOldResolvingWrapper); + } + + private: + XPCCallContext& mCcx; + XPCWrappedNative* mOldResolvingWrapper; +}; + +bool XPC_WN_Helper_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp) { + nsresult rv = NS_OK; + bool retval = true; + bool resolved = false; + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RootedId old(cx, ccx.SetResolveName(id)); + + nsCOMPtr<nsIXPCScriptable> scr = wrapper->GetScriptable(); + + // Resolve a Symbol.hasInstance property if we want custom `instanceof` + // behavior. + if (scr && scr->WantHasInstance() && + id.isWellKnownSymbol(SymbolCode::hasInstance)) { + mozilla::Maybe<AutoSetResolvingWrapper> asrw; + if (scr->AllowPropModsDuringResolve()) { + asrw.emplace(ccx, wrapper); + } + if (!JS_DefineFunctionById( + cx, obj, id, XPC_WN_Helper_HasInstance, 1, + JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) { + rv = NS_ERROR_FAILURE; + } else { + resolved = true; + } + } + + if (scr && scr->WantResolve()) { + mozilla::Maybe<AutoSetResolvingWrapper> asrw; + if (scr->AllowPropModsDuringResolve()) { + asrw.emplace(ccx, wrapper); + } + rv = scr->Resolve(wrapper, cx, obj, id, &resolved, &retval); + } + + old = ccx.SetResolveName(old); + MOZ_ASSERT(old == id, "bad nest"); + + if (NS_FAILED(rv)) { + return Throw(rv, cx); + } + + if (resolved) { + *resolvedp = true; + } else if (wrapper->HasMutatedSet()) { + // We are here if scriptable did not resolve this property and + // it *might* be in the instance set but not the proto set. + + XPCNativeSet* set = wrapper->GetSet(); + XPCNativeSet* protoSet = + wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr; + XPCNativeMember* member = nullptr; + RefPtr<XPCNativeInterface> iface; + bool IsLocal = false; + + if (set->FindMember(id, &member, &iface, protoSet, &IsLocal) && IsLocal) { + XPCWrappedNative* wrapperForInterfaceNames = + (scr && scr->DontReflectInterfaceNames()) ? nullptr : wrapper; + + AutoSetResolvingWrapper asrw(ccx, wrapper); + retval = DefinePropertyIfFound( + ccx, obj, id, set, iface, member, wrapper->GetScope(), false, + wrapperForInterfaceNames, nullptr, scr, JSPROP_ENUMERATE, resolvedp); + } + } + + return retval; +} + +/***************************************************************************/ + +bool XPC_WN_NewEnumerate(JSContext* cx, HandleObject obj, + MutableHandleIdVector properties, + bool enumerableOnly) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + nsCOMPtr<nsIXPCScriptable> scr = wrapper->GetScriptable(); + if (!scr || !scr->WantNewEnumerate()) { + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + } + + if (!XPC_WN_Shared_Enumerate(cx, obj)) { + return false; + } + + bool retval = true; + nsresult rv = + scr->NewEnumerate(wrapper, cx, obj, properties, enumerableOnly, &retval); + if (NS_FAILED(rv)) { + return Throw(rv, cx); + } + return retval; +} + +/***************************************************************************/ +/***************************************************************************/ + +// Compatibility hack. +// +// XPConnect used to do all sorts of funny tricks to find the "correct" +// |this| object for a given method (often to the detriment of proper +// call/apply). When these tricks were removed, a fair amount of chrome +// code broke, because it was relying on being able to grab methods off +// some XPCOM object (like the nsITelemetry service) and invoke them without +// a proper |this|. So, if it's quite clear that we're in this situation and +// about to use a |this| argument that just won't work, fix things up. +// +// This hack is only useful for getters/setters if someone sets an XPCOM object +// as the prototype for a vanilla JS object and expects the XPCOM attributes to +// work on the derived object, which we really don't want to support. But we +// handle it anyway, for now, to minimize regression risk on an already-risky +// landing. +// +// This hack is mainly useful for the NoHelper JSClass. We also fix up +// Components.utils because it implements nsIXPCScriptable (giving it a custom +// JSClass) but not nsIClassInfo (which would put the methods on a prototype). + +#define IS_NOHELPER_CLASS(clasp) (clasp == &XPC_WN_NoHelper_JSClass) +#define IS_CU_CLASS(clasp) \ + (clasp->name[0] == 'n' && !strcmp(clasp->name, "nsXPCComponents_Utils")) + +MOZ_ALWAYS_INLINE JSObject* FixUpThisIfBroken(JSObject* obj, JSObject* funobj) { + if (funobj) { + JSObject* parentObj = + &js::GetFunctionNativeReserved(funobj, XPC_FUNCTION_PARENT_OBJECT_SLOT) + .toObject(); + const JSClass* parentClass = JS::GetClass(parentObj); + if (MOZ_UNLIKELY( + (IS_NOHELPER_CLASS(parentClass) || IS_CU_CLASS(parentClass)) && + (JS::GetClass(obj) != parentClass))) { + return parentObj; + } + } + return obj; +} + +bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, + "bad function"); + RootedObject funobj(cx, &args.callee()); + + RootedObject obj(cx); + if (!args.computeThis(cx, &obj)) { + return false; + } + + obj = FixUpThisIfBroken(obj, funobj); + XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(), + args.array(), vp); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RefPtr<XPCNativeInterface> iface; + XPCNativeMember* member; + + if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) { + return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); + } + ccx.SetCallInfo(iface, member, false); + return XPCWrappedNative::CallMethod(ccx); +} + +bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, + "bad function"); + RootedObject funobj(cx, &args.callee()); + + if (!args.thisv().isObject()) { + JS_ReportErrorASCII( + cx, "xpconnect getter/setter called on incompatible non-object"); + return false; + } + RootedObject obj(cx, &args.thisv().toObject()); + + obj = FixUpThisIfBroken(obj, funobj); + XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(), + args.array(), vp); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RefPtr<XPCNativeInterface> iface; + XPCNativeMember* member; + + if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) { + return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); + } + + if (args.length() != 0 && member->IsWritableAttribute()) { + ccx.SetCallInfo(iface, member, true); + bool retval = XPCWrappedNative::SetAttribute(ccx); + if (retval) { + args.rval().set(args[0]); + } + return retval; + } + // else... + + ccx.SetCallInfo(iface, member, false); + return XPCWrappedNative::GetAttribute(ccx); +} + +/***************************************************************************/ + +/* static */ +XPCWrappedNativeProto* XPCWrappedNativeProto::Get(JSObject* obj) { + MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass); + return JS::GetMaybePtrFromReservedSlot<XPCWrappedNativeProto>(obj, ProtoSlot); +} + +/* static */ +XPCWrappedNativeTearOff* XPCWrappedNativeTearOff::Get(JSObject* obj) { + MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Tearoff_JSClass); + return JS::GetMaybePtrFromReservedSlot<XPCWrappedNativeTearOff>(obj, + TearOffSlot); +} + +static bool XPC_WN_Proto_Enumerate(JSContext* cx, HandleObject obj) { + MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); + XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); + if (!self) { + return false; + } + + XPCNativeSet* set = self->GetSet(); + if (!set) { + return false; + } + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) { + return false; + } + + uint16_t interface_count = set->GetInterfaceCount(); + XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); + for (uint16_t i = 0; i < interface_count; i++) { + XPCNativeInterface* iface = interfaceArray[i]; + uint16_t member_count = iface->GetMemberCount(); + + for (uint16_t k = 0; k < member_count; k++) { + jsid name = iface->GetMemberAt(k)->GetName(); + JS_MarkCrossZoneId(cx, name); + if (!xpc_ForcePropertyResolve(cx, obj, name)) { + return false; + } + } + } + + return true; +} + +static void XPC_WN_Proto_Finalize(JS::GCContext* gcx, JSObject* obj) { + // This can be null if xpc shutdown has already happened + XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); + if (p) { + p->JSProtoObjectFinalized(gcx, obj); + } +} + +static size_t XPC_WN_Proto_ObjectMoved(JSObject* obj, JSObject* old) { + // This can be null if xpc shutdown has already happened + XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj); + if (!p) { + return 0; + } + + p->JSProtoObjectMoved(obj, old); + return 0; +} + +/*****************************************************/ + +static bool XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx, + HandleObject obj, + HandleId id, + HandleValue v) { + MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); + + XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); + if (!self) { + return false; + } + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) { + return false; + } + + // Allow XPConnect to add the property only + if (ccx.GetResolveName() == id) { + return true; + } + + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); +} + +static bool XPC_WN_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp) { + MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto"); + + XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj); + if (!self) { + return false; + } + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) { + return false; + } + + nsCOMPtr<nsIXPCScriptable> scr = self->GetScriptable(); + + return DefinePropertyIfFound( + ccx, obj, id, self->GetSet(), nullptr, nullptr, self->GetScope(), true, + nullptr, nullptr, scr, + JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, resolvedp); +} + +static const JSClassOps XPC_WN_Proto_JSClassOps = { + XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty + XPC_WN_CannotDeletePropertyStub, // delProperty + XPC_WN_Proto_Enumerate, // enumerate + nullptr, // newEnumerate + XPC_WN_Proto_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_Proto_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +static const js::ClassExtension XPC_WN_Proto_ClassExtension = { + XPC_WN_Proto_ObjectMoved, // objectMovedOp +}; + +const JSClass XPC_WN_Proto_JSClass = { + "XPC_WN_Proto_JSClass", + JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeProto::SlotCount) | + JSCLASS_FOREGROUND_FINALIZE, + &XPC_WN_Proto_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_Proto_ClassExtension, + JS_NULL_OBJECT_OPS}; + +/***************************************************************************/ + +static bool XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCWrappedNativeTearOff* to = ccx.GetTearOff(); + XPCNativeInterface* iface; + + if (!to || nullptr == (iface = to->GetInterface())) { + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + } + + uint16_t member_count = iface->GetMemberCount(); + for (uint16_t k = 0; k < member_count; k++) { + jsid name = iface->GetMemberAt(k)->GetName(); + JS_MarkCrossZoneId(cx, name); + if (!xpc_ForcePropertyResolve(cx, obj, name)) { + return false; + } + } + + return true; +} + +static bool XPC_WN_TearOff_Resolve(JSContext* cx, HandleObject obj, HandleId id, + bool* resolvedp) { + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCWrappedNativeTearOff* to = ccx.GetTearOff(); + XPCNativeInterface* iface; + + if (!to || nullptr == (iface = to->GetInterface())) { + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + } + + return DefinePropertyIfFound( + ccx, obj, id, nullptr, iface, nullptr, wrapper->GetScope(), true, nullptr, + nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, + resolvedp); +} + +static void XPC_WN_TearOff_Finalize(JS::GCContext* gcx, JSObject* obj) { + XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj); + if (!p) { + return; + } + p->JSObjectFinalized(); +} + +static size_t XPC_WN_TearOff_ObjectMoved(JSObject* obj, JSObject* old) { + XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj); + if (!p) { + return 0; + } + p->JSObjectMoved(obj, old); + return 0; +} + +static const JSClassOps XPC_WN_Tearoff_JSClassOps = { + XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty + XPC_WN_CannotDeletePropertyStub, // delProperty + XPC_WN_TearOff_Enumerate, // enumerate + nullptr, // newEnumerate + XPC_WN_TearOff_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_TearOff_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = { + XPC_WN_TearOff_ObjectMoved, // objectMovedOp +}; + +const JSClass XPC_WN_Tearoff_JSClass = { + "WrappedNative_TearOff", + JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeTearOff::SlotCount) | + JSCLASS_FOREGROUND_FINALIZE, + &XPC_WN_Tearoff_JSClassOps, JS_NULL_CLASS_SPEC, + &XPC_WN_Tearoff_JSClassExtension}; diff --git a/js/xpconnect/src/XPCWrappedNativeProto.cpp b/js/xpconnect/src/XPCWrappedNativeProto.cpp new file mode 100644 index 0000000000..4b492bfa9e --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +/* Shared proto object for XPCWrappedNative. */ + +#include "xpcprivate.h" +#include "js/Object.h" // JS::SetReservedSlot +#include "pratom.h" +#include "XPCMaps.h" + +using namespace mozilla; + +#ifdef DEBUG +int32_t XPCWrappedNativeProto::gDEBUG_LiveProtoCount = 0; +#endif + +XPCWrappedNativeProto::XPCWrappedNativeProto(XPCWrappedNativeScope* Scope, + nsIClassInfo* ClassInfo, + RefPtr<XPCNativeSet>&& Set) + : mScope(Scope), + mJSProtoObject(nullptr), + mClassInfo(ClassInfo), + mSet(std::move(Set)) { + // This native object lives as long as its associated JSObject - killed + // by finalization of the JSObject (or explicitly if Init fails). + + MOZ_COUNT_CTOR(XPCWrappedNativeProto); + MOZ_ASSERT(mScope); + +#ifdef DEBUG + gDEBUG_LiveProtoCount++; +#endif +} + +XPCWrappedNativeProto::~XPCWrappedNativeProto() { + MOZ_ASSERT(!mJSProtoObject, "JSProtoObject still alive"); + + MOZ_COUNT_DTOR(XPCWrappedNativeProto); + +#ifdef DEBUG + gDEBUG_LiveProtoCount--; +#endif + + // Note that our weak ref to mScope is not to be trusted at this point. + + XPCNativeSet::ClearCacheEntryForClassInfo(mClassInfo); + + DeferredFinalize(mClassInfo.forget().take()); +} + +bool XPCWrappedNativeProto::Init(JSContext* cx, nsIXPCScriptable* scriptable) { + mScriptable = scriptable; + + JS::RootedObject proto(cx, JS::GetRealmObjectPrototype(cx)); + mJSProtoObject = JS_NewObjectWithGivenProto(cx, &XPC_WN_Proto_JSClass, proto); + + bool success = !!mJSProtoObject; + if (success) { + JS::SetReservedSlot(mJSProtoObject, ProtoSlot, JS::PrivateValue(this)); + } + + return success; +} + +void XPCWrappedNativeProto::JSProtoObjectFinalized(JS::GCContext* gcx, + JSObject* obj) { + MOZ_ASSERT(obj == mJSProtoObject, "huh?"); + +#ifdef DEBUG + // Check that this object has already been swept from the map. + ClassInfo2WrappedNativeProtoMap* map = GetScope()->GetWrappedNativeProtoMap(); + MOZ_ASSERT(map->Find(mClassInfo) != this); +#endif + + MOZ_ALWAYS_TRUE(GetRuntime()->GetDyingWrappedNativeProtos().append(this)); + mJSProtoObject = nullptr; +} + +void XPCWrappedNativeProto::JSProtoObjectMoved(JSObject* obj, + const JSObject* old) { + // Update without triggering barriers. + MOZ_ASSERT(mJSProtoObject == old); + mJSProtoObject.unbarrieredSet(obj); +} + +void XPCWrappedNativeProto::SystemIsBeingShutDown() { + // Note that the instance might receive this call multiple times + // as we walk to here from various places. + + if (mJSProtoObject) { + // short circuit future finalization + JS::SetReservedSlot(mJSProtoObject, ProtoSlot, JS::UndefinedValue()); + mJSProtoObject = nullptr; + } +} + +// static +XPCWrappedNativeProto* XPCWrappedNativeProto::GetNewOrUsed( + JSContext* cx, XPCWrappedNativeScope* scope, nsIClassInfo* classInfo, + nsIXPCScriptable* scriptable) { + MOZ_ASSERT(scope, "bad param"); + MOZ_ASSERT(classInfo, "bad param"); + + AutoMarkingWrappedNativeProtoPtr proto(cx); + ClassInfo2WrappedNativeProtoMap* map = nullptr; + + map = scope->GetWrappedNativeProtoMap(); + proto = map->Find(classInfo); + if (proto) { + return proto; + } + + RefPtr<XPCNativeSet> set = XPCNativeSet::GetNewOrUsed(cx, classInfo); + if (!set) { + return nullptr; + } + + proto = new XPCWrappedNativeProto(scope, classInfo, std::move(set)); + + if (!proto->Init(cx, scriptable)) { + delete proto.get(); + return nullptr; + } + + map->Add(classInfo, proto); + + return proto; +} + +void XPCWrappedNativeProto::DebugDump(int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCWrappedNativeProto @ %p", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("gDEBUG_LiveProtoCount is %d", gDEBUG_LiveProtoCount)); + XPC_LOG_ALWAYS(("mScope @ %p", mScope)); + XPC_LOG_ALWAYS(("mJSProtoObject @ %p", mJSProtoObject.get())); + XPC_LOG_ALWAYS(("mSet @ %p", mSet.get())); + XPC_LOG_ALWAYS(("mScriptable @ %p", mScriptable.get())); + if (depth && mScriptable) { + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mFlags of %x", mScriptable->GetScriptableFlags())); + XPC_LOG_ALWAYS(("mJSClass @ %p", mScriptable->GetJSClass())); + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif +} diff --git a/js/xpconnect/src/XPCWrappedNativeScope.cpp b/js/xpconnect/src/XPCWrappedNativeScope.cpp new file mode 100644 index 0000000000..c600760544 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp @@ -0,0 +1,497 @@ +/* -*- 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/. */ + +/* Class used to manage the wrapped native objects within a JS scope. */ + +#include "AccessCheck.h" +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "ExpandedPrincipal.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "XPCMaps.h" +#include "mozilla/Unused.h" +#include "js/Object.h" // JS::GetCompartment +#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById +#include "js/RealmIterators.h" +#include "mozJSModuleLoader.h" + +#include "mozilla/dom/BindingUtils.h" + +using namespace mozilla; +using namespace xpc; +using namespace JS; + +/***************************************************************************/ + +static XPCWrappedNativeScopeList& AllScopes() { + return XPCJSRuntime::Get()->GetWrappedNativeScopes(); +} + +static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) { + // AllowXULXBLForPrincipal will return true for system principal, but we + // don't want that here. + MOZ_ASSERT(nsContentUtils::IsInitialized()); + if (aPrincipal->IsSystemPrincipal()) { + return false; + } + + // If this domain isn't whitelisted, we're done. + if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) { + return false; + } + + // Check the pref to determine how we should behave. + return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); +} + +static bool RemoteXULForbidsXBLScope(HandleObject aFirstGlobal) { + MOZ_ASSERT(aFirstGlobal); + + // Certain singleton sandoxes are created very early in startup - too early + // to call into AllowXULXBLForPrincipal. We never create XBL scopes for + // sandboxes anway, and certainly not for these singleton scopes. So we just + // short-circuit here. + if (IsSandbox(aFirstGlobal)) { + return false; + } + + nsIPrincipal* principal = xpc::GetObjectPrincipal(aFirstGlobal); + return RemoteXULForbidsXBLScopeForPrincipal(principal); +} + +XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment, + JS::HandleObject aFirstGlobal) + : mWrappedNativeMap(mozilla::MakeUnique<Native2WrappedNativeMap>()), + mWrappedNativeProtoMap( + mozilla::MakeUnique<ClassInfo2WrappedNativeProtoMap>()), + mComponents(nullptr), + mCompartment(aCompartment) { +#ifdef DEBUG + for (XPCWrappedNativeScope* cur : AllScopes()) { + MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object"); + } +#endif + + AllScopes().insertBack(this); + + MOZ_COUNT_CTOR(XPCWrappedNativeScope); + + // Determine whether we would allow an XBL scope in this situation. + // In addition to being pref-controlled, we also disable XBL scopes for + // remote XUL domains, _except_ if we have an additional pref override set. + // + // Note that we can't quite remove this yet, even though we never actually + // use XBL scopes, because the security manager uses this boolean to make + // decisions that we rely on in our test infrastructure. + // + // FIXME(emilio): Now that the security manager is the only caller probably + // should be renamed, but what's a good name for this? + mAllowContentXBLScope = !RemoteXULForbidsXBLScope(aFirstGlobal); +} + +bool XPCWrappedNativeScope::GetComponentsJSObject(JSContext* cx, + JS::MutableHandleObject obj) { + if (!mComponents) { + bool system = AccessCheck::isChrome(mCompartment); + MOZ_RELEASE_ASSERT(system, "How did we get a non-system Components?"); + mComponents = new nsXPCComponents(this); + } + + RootedValue val(cx); + xpcObjectHelper helper(mComponents); + bool ok = XPCConvert::NativeInterface2JSObject(cx, &val, helper, nullptr, + false, nullptr); + if (NS_WARN_IF(!ok)) { + return false; + } + + if (NS_WARN_IF(!val.isObject())) { + return false; + } + + obj.set(&val.toObject()); + return true; +} + +static bool DefineSubcomponentProperty(JSContext* aCx, HandleObject aGlobal, + nsISupports* aSubcomponent, + const nsID* aIID, + unsigned int aStringIndex) { + RootedValue subcompVal(aCx); + xpcObjectHelper helper(aSubcomponent); + if (!XPCConvert::NativeInterface2JSObject(aCx, &subcompVal, helper, aIID, + false, nullptr)) + return false; + if (NS_WARN_IF(!subcompVal.isObject())) { + return false; + } + RootedId id(aCx, XPCJSContext::Get()->GetStringID(aStringIndex)); + return JS_DefinePropertyById(aCx, aGlobal, id, subcompVal, 0); +} + +bool XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) { + RootedObject components(aCx); + if (!GetComponentsJSObject(aCx, &components)) { + return false; + } + + RootedObject global(aCx, CurrentGlobalOrNull(aCx)); + + const unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING | JSPROP_PERMANENT; + + RootedId id(aCx, + XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)); + if (!JS_DefinePropertyById(aCx, global, id, components, attrs)) { + return false; + } + +// _iid can be nullptr if the object implements classinfo. +#define DEFINE_SUBCOMPONENT_PROPERTY(_comp, _type, _iid, _id) \ + nsCOMPtr<nsIXPCComponents_##_type> obj##_type; \ + if (NS_FAILED(_comp->Get##_type(getter_AddRefs(obj##_type)))) return false; \ + if (!DefineSubcomponentProperty(aCx, global, obj##_type, _iid, \ + XPCJSContext::IDX_##_id)) \ + return false; + + DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Interfaces, nullptr, CI) + DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Results, nullptr, CR) + + DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Classes, nullptr, CC) + DEFINE_SUBCOMPONENT_PROPERTY(mComponents, Utils, + &NS_GET_IID(nsIXPCComponents_Utils), CU) + +#undef DEFINE_SUBCOMPONENT_PROPERTY + + return true; +} + +bool XPCWrappedNativeScope::AttachJSServices(JSContext* aCx) { + RootedObject global(aCx, CurrentGlobalOrNull(aCx)); + return mozJSModuleLoader::Get()->DefineJSServices(aCx, global); +} + +bool XPCWrappedNativeScope::XBLScopeStateMatches(nsIPrincipal* aPrincipal) { + return mAllowContentXBLScope == + !RemoteXULForbidsXBLScopeForPrincipal(aPrincipal); +} + +bool XPCWrappedNativeScope::AllowContentXBLScope(Realm* aRealm) { + // We only disallow XBL scopes in remote XUL situations. + MOZ_ASSERT_IF(!mAllowContentXBLScope, nsContentUtils::AllowXULXBLForPrincipal( + xpc::GetRealmPrincipal(aRealm))); + return mAllowContentXBLScope; +} + +namespace xpc { +JSObject* GetUAWidgetScope(JSContext* cx, JSObject* contentScopeArg) { + JS::RootedObject contentScope(cx, contentScopeArg); + JSAutoRealm ar(cx, contentScope); + nsIPrincipal* principal = GetObjectPrincipal(contentScope); + + if (principal->IsSystemPrincipal()) { + return JS::GetNonCCWObjectGlobal(contentScope); + } + + return GetUAWidgetScope(cx, principal); +} + +JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal) { + RootedObject scope(cx, XPCJSRuntime::Get()->GetUAWidgetScope(cx, principal)); + NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. + + scope = js::UncheckedUnwrap(scope); + JS::ExposeObjectToActiveJS(scope); + return scope; +} + +bool AllowContentXBLScope(JS::Realm* realm) { + JS::Compartment* comp = GetCompartmentForRealm(realm); + XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->GetScope(); + MOZ_ASSERT(scope); + return scope->AllowContentXBLScope(realm); +} + +} /* namespace xpc */ + +XPCWrappedNativeScope::~XPCWrappedNativeScope() { + MOZ_COUNT_DTOR(XPCWrappedNativeScope); + + // We can do additional cleanup assertions here... + + MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map"); + + MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map"); + + // This should not be necessary, since the Components object should die + // with the scope but just in case. + if (mComponents) { + mComponents->mScope = nullptr; + } + + // XXX we should assert that we are dead or that xpconnect has shutdown + // XXX might not want to do this at xpconnect shutdown time??? + mComponents = nullptr; + + MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized()); + + mCompartment = nullptr; +} + +// static +void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt, + JSTracer* trc) { + // Do JS::TraceEdge for all wrapped natives with external references, as + // well as any DOM expando objects. + // + // Note: the GC can call this from a JS helper thread. We don't use + // AllScopes() because that asserts we're on the main thread. + + for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { + XPCWrappedNative* wrapper = i.get().value(); + if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) { + wrapper->TraceSelf(trc); + } + } + } +} + +// static +void XPCWrappedNativeScope::SuspectAllWrappers( + nsCycleCollectionNoteRootCallback& cb) { + for (XPCWrappedNativeScope* cur : AllScopes()) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { + i.get().value()->Suspect(cb); + } + } +} + +void XPCWrappedNativeScope::UpdateWeakPointersAfterGC(JSTracer* trc) { + // Sweep waivers. + if (mWaiverWrapperMap) { + mWaiverWrapperMap->UpdateWeakPointers(trc); + } + + if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) { + return; + } + + if (!js::CompartmentHasLiveGlobal(mCompartment)) { + GetWrappedNativeMap()->Clear(); + mWrappedNativeProtoMap->Clear(); + + // The fields below are traced only if there's a live global in the + // compartment, see TraceXPCGlobal. The compartment has no live globals so + // clear these pointers here. + if (mXrayExpandos.initialized()) { + mXrayExpandos.destroy(); + } + mIDProto = nullptr; + mIIDProto = nullptr; + mCIDProto = nullptr; + return; + } + + // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already + // been handled by XPCWrappedNative::FlatJSObjectMoved. + for (auto iter = GetWrappedNativeMap()->ModIter(); !iter.done(); + iter.next()) { + XPCWrappedNative* wrapper = iter.get().value(); + JSObject* obj = wrapper->GetFlatJSObjectPreserveColor(); + if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) { + MOZ_ASSERT(obj == wrapper->GetFlatJSObjectPreserveColor()); + MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment); + } else { + iter.remove(); + } + } + + // Sweep mWrappedNativeProtoMap for dying prototype JSObjects. Moving has + // already been handled by XPCWrappedNativeProto::JSProtoObjectMoved. + for (auto i = mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) { + XPCWrappedNativeProto* proto = i.get().value(); + JSObject* obj = proto->GetJSProtoObjectPreserveColor(); + if (JS_UpdateWeakPointerAfterGCUnbarriered(trc, &obj)) { + MOZ_ASSERT(JS::GetCompartment(obj) == mCompartment); + MOZ_ASSERT(obj == proto->GetJSProtoObjectPreserveColor()); + } else { + i.remove(); + } + } +} + +// static +void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() { + for (XPCWrappedNativeScope* cur : AllScopes()) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.done(); i.next()) { + i.get().value()->SweepTearOffs(); + } + } +} + +// static +void XPCWrappedNativeScope::SystemIsBeingShutDown() { + // We're forcibly killing scopes, rather than allowing them to go away + // when they're ready. As such, we need to do some cleanup before they + // can safely be destroyed. + + for (XPCWrappedNativeScope* cur : AllScopes()) { + // Give the Components object a chance to try to clean up. + if (cur->mComponents) { + cur->mComponents->SystemIsBeingShutDown(); + } + + // Null out these pointers to prevent ~ObjectPtr assertion failures if we + // leaked things at shutdown. + cur->mIDProto = nullptr; + cur->mIIDProto = nullptr; + cur->mCIDProto = nullptr; + + // Similarly, destroy mXrayExpandos to prevent assertion failures. + if (cur->mXrayExpandos.initialized()) { + cur->mXrayExpandos.destroy(); + } + + // Walk the protos first. Wrapper shutdown can leave dangling + // proto pointers in the proto map. + for (auto i = cur->mWrappedNativeProtoMap->ModIter(); !i.done(); i.next()) { + i.get().value()->SystemIsBeingShutDown(); + i.remove(); + } + for (auto i = cur->mWrappedNativeMap->ModIter(); !i.done(); i.next()) { + i.get().value()->SystemIsBeingShutDown(); + i.remove(); + } + + CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment()); + priv->SystemIsBeingShutDown(); + } +} + +/***************************************************************************/ + +JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) { + MOZ_ASSERT(ObjectScope(target) == this); + if (!mXrayExpandos.initialized()) { + return nullptr; + } + return mXrayExpandos.lookup(target); +} + +JSObject* XPCWrappedNativeScope::DetachExpandoChain(HandleObject target) { + MOZ_ASSERT(ObjectScope(target) == this); + if (!mXrayExpandos.initialized()) { + return nullptr; + } + return mXrayExpandos.removeValue(target); +} + +bool XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target, + HandleObject chain) { + MOZ_ASSERT(ObjectScope(target) == this); + MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); + MOZ_ASSERT_IF(chain, ObjectScope(chain) == this); + if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) { + return false; + } + return mXrayExpandos.put(cx, target, chain); +} + +/***************************************************************************/ + +// static +void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) { +#ifdef DEBUG + depth--; + + // get scope count. + int count = 0; + for (XPCWrappedNativeScope* cur : AllScopes()) { + mozilla::Unused << cur; + count++; + } + + XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count)); + XPC_LOG_INDENT(); + if (depth) { + for (XPCWrappedNativeScope* cur : AllScopes()) { + cur->DebugDump(depth); + } + } + XPC_LOG_OUTDENT(); +#endif +} + +void XPCWrappedNativeScope::DebugDump(int16_t depth) { +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("next @ %p", getNext())); + XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get())); + XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment)); + + XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)", + mWrappedNativeMap.get(), mWrappedNativeMap->Count())); + // iterate contexts... + if (depth && mWrappedNativeMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mWrappedNativeMap->Iter(); !i.done(); i.next()) { + i.get().value()->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + + XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %p with %d protos(s)", + mWrappedNativeProtoMap.get(), + mWrappedNativeProtoMap->Count())); + // iterate contexts... + if (depth && mWrappedNativeProtoMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mWrappedNativeProtoMap->Iter(); !i.done(); i.next()) { + i.get().value()->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif +} + +void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis( + JSContext* cx, ScopeSizeInfo* scopeSizeInfo) { + for (XPCWrappedNativeScope* cur : AllScopes()) { + cur->AddSizeOfIncludingThis(cx, scopeSizeInfo); + } +} + +void XPCWrappedNativeScope::AddSizeOfIncludingThis( + JSContext* cx, ScopeSizeInfo* scopeSizeInfo) { + scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this); + scopeSizeInfo->mScopeAndMapSize += + mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + scopeSizeInfo->mScopeAndMapSize += + mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + + auto realmCb = [](JSContext*, void* aData, JS::Realm* aRealm, + const JS::AutoRequireNoGC& nogc) { + auto* scopeSizeInfo = static_cast<ScopeSizeInfo*>(aData); + JSObject* global = GetRealmGlobalOrNull(aRealm); + if (global && dom::HasProtoAndIfaceCache(global)) { + dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(global); + scopeSizeInfo->mProtoAndIfaceCacheSize += + cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + } + }; + IterateRealmsInCompartment(cx, Compartment(), scopeSizeInfo, realmCb); + + // There are other XPCWrappedNativeScope members that could be measured; + // the above ones have been seen by DMD to be worth measuring. More stuff + // may be added later. +} diff --git a/js/xpconnect/src/XPCWrapper.cpp b/js/xpconnect/src/XPCWrapper.cpp new file mode 100644 index 0000000000..7a4f689471 --- /dev/null +++ b/js/xpconnect/src/XPCWrapper.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "WrapperFactory.h" +#include "AccessCheck.h" + +#include "js/PropertyAndElement.h" // JS_DefineFunction + +using namespace xpc; +using namespace mozilla; +using namespace JS; + +namespace XPCNativeWrapper { + +static inline bool ThrowException(nsresult ex, JSContext* cx) { + XPCThrower::Throw(ex, cx); + + return false; +} + +static bool UnwrapNW(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ThrowException(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx); + } + + JS::RootedValue v(cx, args[0]); + if (!v.isObject() || !js::IsCrossCompartmentWrapper(&v.toObject()) || + !WrapperFactory::AllowWaiver(&v.toObject())) { + args.rval().set(v); + return true; + } + + bool ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); + NS_ENSURE_TRUE(ok, false); + args.rval().set(v); + return true; +} + +static bool XrayWrapperConstructor(JSContext* cx, unsigned argc, Value* vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() == 0) { + return ThrowException(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx); + } + + if (!args[0].isObject()) { + if (args.isConstructing()) { + return ThrowException(NS_ERROR_XPC_BAD_CONVERT_JS, cx); + } + + args.rval().set(args[0]); + return true; + } + + args.rval().setObject(*js::UncheckedUnwrap(&args[0].toObject())); + return JS_WrapValue(cx, args.rval()); +} +// static +bool AttachNewConstructorObject(JSContext* aCx, + JS::HandleObject aGlobalObject) { + JSAutoRealm ar(aCx, aGlobalObject); + JSFunction* xpcnativewrapper = JS_DefineFunction( + aCx, aGlobalObject, "XPCNativeWrapper", XrayWrapperConstructor, 1, + JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_CONSTRUCTOR); + if (!xpcnativewrapper) { + return false; + } + JS::RootedObject obj(aCx, JS_GetFunctionObject(xpcnativewrapper)); + return JS_DefineFunction(aCx, obj, "unwrap", UnwrapNW, 1, + JSPROP_READONLY | JSPROP_PERMANENT) != nullptr; +} + +} // namespace XPCNativeWrapper + +namespace XPCWrapper { + +JSObject* UnsafeUnwrapSecurityWrapper(JSObject* obj) { + if (js::IsProxy(obj)) { + return js::UncheckedUnwrap(obj); + } + + return obj; +} + +} // namespace XPCWrapper diff --git a/js/xpconnect/src/XPCWrapper.h b/js/xpconnect/src/XPCWrapper.h new file mode 100644 index 0000000000..3c95322918 --- /dev/null +++ b/js/xpconnect/src/XPCWrapper.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +#ifndef XPC_WRAPPER_H +#define XPC_WRAPPER_H 1 + +#include "js/TypeDecls.h" + +namespace XPCNativeWrapper { + +bool AttachNewConstructorObject(JSContext* aCx, JS::HandleObject aGlobalObject); + +} // namespace XPCNativeWrapper + +// This namespace wraps some common functionality between the three existing +// wrappers. Its main purpose is to allow XPCCrossOriginWrapper to act both +// as an XPCSafeJSObjectWrapper and as an XPCNativeWrapper when required to +// do so (the decision is based on the principals of the wrapper and wrapped +// objects). +namespace XPCWrapper { + +JSObject* UnsafeUnwrapSecurityWrapper(JSObject* obj); + +} // namespace XPCWrapper + +#endif diff --git a/js/xpconnect/src/components.conf b/js/xpconnect/src/components.conf new file mode 100644 index 0000000000..5cccec28b2 --- /dev/null +++ b/js/xpconnect/src/components.conf @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'js_name': 'scriptloader', + 'cid': '{929814d6-1dd2-11b2-8e08-82fa0a339b00}', + 'contract_ids': ['@mozilla.org/moz/jssubscript-loader;1'], + 'interfaces': ['mozIJSSubScriptLoader'], + 'type': 'mozJSSubScriptLoader', + 'headers': ['/js/xpconnect/loader/mozJSSubScriptLoader.h'], + }, +] diff --git a/js/xpconnect/src/jsshell.msg b/js/xpconnect/src/jsshell.msg new file mode 100644 index 0000000000..2758c1276c --- /dev/null +++ b/js/xpconnect/src/jsshell.msg @@ -0,0 +1,12 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* + * Error messages for JSShell. See js/public/friend/ErrorNumbers.msg for format. + */ + +MSG_DEF(JSSMSG_NOT_AN_ERROR, 0, 0, JSEXN_ERR, "<Error #0 is reserved>") +MSG_DEF(JSSMSG_CANT_OPEN, 1, 2, JSEXN_ERR, "can't open {0}: {1}") diff --git a/js/xpconnect/src/moz.build b/js/xpconnect/src/moz.build new file mode 100644 index 0000000000..39d4baecec --- /dev/null +++ b/js/xpconnect/src/moz.build @@ -0,0 +1,79 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "BackstagePass.h", + "JSServices.h", + "nsIXPConnect.h", + "XPCJSMemoryReporter.h", + "xpcObjectHelper.h", + "xpcpublic.h", + "XPCSelfHostedShmem.h", +] + +UNIFIED_SOURCES += [ + "ExportHelpers.cpp", + "JSServices.cpp", + "nsXPConnect.cpp", + "Sandbox.cpp", + "XPCCallContext.cpp", + "XPCComponents.cpp", + "XPCConvert.cpp", + "XPCDebug.cpp", + "XPCException.cpp", + "XPCJSContext.cpp", + "XPCJSID.cpp", + "XPCJSRuntime.cpp", + "XPCJSWeakReference.cpp", + "XPCLocale.cpp", + "XPCLog.cpp", + "XPCMaps.cpp", + "XPCModule.cpp", + "XPCRuntimeService.cpp", + "XPCSelfHostedShmem.cpp", + "XPCShellImpl.cpp", + "XPCString.cpp", + "XPCThrower.cpp", + "XPCVariant.cpp", + "XPCWrappedJS.cpp", + "XPCWrappedJSClass.cpp", + "XPCWrappedJSIterator.cpp", + "XPCWrappedNative.cpp", + "XPCWrappedNativeInfo.cpp", + "XPCWrappedNativeJSOps.cpp", + "XPCWrappedNativeProto.cpp", + "XPCWrappedNativeScope.cpp", + "XPCWrapper.cpp", +] + + +if CONFIG["LIBFUZZER"]: + UNIFIED_SOURCES += ["xpcrtfuzzing/xpcrtfuzzing.cpp"] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!/xpcom/components", + "../loader", + "../wrappers", + "/caps", + "/dom/base", + "/dom/bindings", + "/dom/html", + "/layout/base", + "/layout/style", + "/netwerk/base", + "/xpcom/components", +] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Werror=format"] diff --git a/js/xpconnect/src/nsIXPConnect.h b/js/xpconnect/src/nsIXPConnect.h new file mode 100644 index 0000000000..07703182f4 --- /dev/null +++ b/js/xpconnect/src/nsIXPConnect.h @@ -0,0 +1,291 @@ +/* -*- 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/. */ + +#ifndef nsIXPConnect_h +#define nsIXPConnect_h + +/* The core XPConnect public interfaces. */ + +#include "nsISupports.h" + +#include "jspubtd.h" +#include "js/CompileOptions.h" +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "xptinfo.h" +#include "nsCOMPtr.h" + +class XPCWrappedNative; +class nsXPCWrappedJS; +class nsWrapperCache; + +// forward declarations... +class nsIPrincipal; +class nsIVariant; + +/***************************************************************************/ +#define NS_IXPCONNECTJSOBJECTHOLDER_IID_STR \ + "73e6ff4a-ab99-4d99-ac00-ba39ccb8e4d7" +#define NS_IXPCONNECTJSOBJECTHOLDER_IID \ + { \ + 0x73e6ff4a, 0xab99, 0x4d99, { \ + 0xac, 0x00, 0xba, 0x39, 0xcc, 0xb8, 0xe4, 0xd7 \ + } \ + } + +class NS_NO_VTABLE nsIXPConnectJSObjectHolder : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECTJSOBJECTHOLDER_IID) + + virtual JSObject* GetJSObject() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPConnectJSObjectHolder, + NS_IXPCONNECTJSOBJECTHOLDER_IID) + +#define NS_IXPCONNECTWRAPPEDNATIVE_IID_STR \ + "e787be29-db5d-4a45-a3d6-1de1d6b85c30" +#define NS_IXPCONNECTWRAPPEDNATIVE_IID \ + { \ + 0xe787be29, 0xdb5d, 0x4a45, { \ + 0xa3, 0xd6, 0x1d, 0xe1, 0xd6, 0xb8, 0x5c, 0x30 \ + } \ + } + +class nsIXPConnectWrappedNative : public nsIXPConnectJSObjectHolder { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECTWRAPPEDNATIVE_IID) + + nsresult DebugDump(int16_t depth); + + nsISupports* Native() const { return mIdentity; } + + protected: + nsCOMPtr<nsISupports> mIdentity; + + private: + XPCWrappedNative* AsXPCWrappedNative(); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPConnectWrappedNative, + NS_IXPCONNECTWRAPPEDNATIVE_IID) + +#define NS_IXPCONNECTWRAPPEDJS_IID_STR "3a01b0d6-074b-49ed-bac3-08c76366cae4" +#define NS_IXPCONNECTWRAPPEDJS_IID \ + { \ + 0x3a01b0d6, 0x074b, 0x49ed, { \ + 0xba, 0xc3, 0x08, 0xc7, 0x63, 0x66, 0xca, 0xe4 \ + } \ + } + +class nsIXPConnectWrappedJS : public nsIXPConnectJSObjectHolder { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECTWRAPPEDJS_IID) + + nsresult GetInterfaceIID(nsIID** aInterfaceIID); + + // Returns the global object for our JS object. If this object is a + // cross-compartment wrapper, returns the compartment's first global. + // The global we return is guaranteed to be same-compartment with the + // object. + // Note: this matches the GetJSObject() signature. + JSObject* GetJSObjectGlobal(); + + nsresult DebugDump(int16_t depth); + + nsresult AggregatedQueryInterface(const nsIID& aIID, void** aInstancePtr); + + private: + nsXPCWrappedJS* AsXPCWrappedJS(); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPConnectWrappedJS, NS_IXPCONNECTWRAPPEDJS_IID) + +#define NS_IXPCONNECTWRAPPEDJSUNMARKGRAY_IID_STR \ + "c02a0ce6-275f-4ea1-9c23-08494898b070" +#define NS_IXPCONNECTWRAPPEDJSUNMARKGRAY_IID \ + { \ + 0xc02a0ce6, 0x275f, 0x4ea1, { \ + 0x9c, 0x23, 0x08, 0x49, 0x48, 0x98, 0xb0, 0x70 \ + } \ + } + +// Special interface to unmark the internal JSObject. +// QIing to nsIXPConnectWrappedJSUnmarkGray does *not* addref, it only unmarks, +// and QIing to nsIXPConnectWrappedJSUnmarkGray is always supposed to fail. +class NS_NO_VTABLE nsIXPConnectWrappedJSUnmarkGray + : public nsIXPConnectWrappedJS { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECTWRAPPEDJSUNMARKGRAY_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPConnectWrappedJSUnmarkGray, + NS_IXPCONNECTWRAPPEDJSUNMARKGRAY_IID) + +/***************************************************************************/ + +#define NS_IXPCONNECT_IID_STR "768507b5-b981-40c7-8276-f6a1da502a24" +#define NS_IXPCONNECT_IID \ + { \ + 0x768507b5, 0xb981, 0x40c7, { \ + 0x82, 0x76, 0xf6, 0xa1, 0xda, 0x50, 0x2a, 0x24 \ + } \ + } + +class nsIXPConnect : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECT_IID) + // This gets a non-addref'd pointer. + static nsIXPConnect* XPConnect(); + + /** + * wrapNative will create a new JSObject or return an existing one. + * + * This method now correctly deals with cases where the passed in xpcom + * object already has an associated JSObject for the cases: + * 1) The xpcom object has already been wrapped for use in the same scope + * as an nsIXPConnectWrappedNative. + * 2) The xpcom object is in fact a nsIXPConnectWrappedJS and thus already + * has an underlying JSObject. + * + * It *might* be possible to QueryInterface the nsIXPConnectJSObjectHolder + * returned by the method into a nsIXPConnectWrappedNative or a + * nsIXPConnectWrappedJS. + * + * This method will never wrap the JSObject involved in an + * XPCNativeWrapper before returning. + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_NATIVE + * NS_ERROR_FAILURE + */ + nsresult WrapNative(JSContext* aJSContext, JSObject* aScopeArg, + nsISupports* aCOMObj, const nsIID& aIID, + JSObject** aRetVal); + + /** + * Same as wrapNative, but it returns the JSObject in aVal. C++ callers + * must ensure that aVal is rooted. + * aIID may be null, it means the same as passing in + * &NS_GET_IID(nsISupports) but when passing in null certain shortcuts + * can be taken because we know without comparing IIDs that the caller is + * asking for an nsISupports wrapper. + * If aAllowWrapper, then the returned value will be wrapped in the proper + * type of security wrapper on top of the XPCWrappedNative (if needed). + * This method doesn't push aJSContext on the context stack, so the caller + * is required to push it if the top of the context stack is not equal to + * aJSContext. + */ + nsresult WrapNativeToJSVal(JSContext* aJSContext, JSObject* aScopeArg, + nsISupports* aCOMObj, nsWrapperCache* aCache, + const nsIID* aIID, bool aAllowWrapping, + JS::MutableHandle<JS::Value> aVal); + + /** + * wrapJS will yield a new or previously existing xpcom interface pointer + * to represent the JSObject passed in. + * + * This method now correctly deals with cases where the passed in JSObject + * already has an associated xpcom interface for the cases: + * 1) The JSObject has already been wrapped as a nsIXPConnectWrappedJS. + * 2) The JSObject is in fact a nsIXPConnectWrappedNative and thus already + * has an underlying xpcom object. + * 3) The JSObject is of a jsclass which supports getting the nsISupports + * from the JSObject directly. This is used for idlc style objects + * (e.g. DOM objects). + * + * It *might* be possible to QueryInterface the resulting interface pointer + * to nsIXPConnectWrappedJS. + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_JS + * NS_ERROR_FAILURE + */ + nsresult WrapJS(JSContext* aJSContext, JSObject* aJSObj, const nsIID& aIID, + void** result); + + /** + * Wraps the given jsval in a nsIVariant and returns the new variant. + */ + nsresult JSValToVariant(JSContext* cx, JS::Handle<JS::Value> aJSVal, + nsIVariant** aResult); + + /** + * This only succeeds if the JSObject is a nsIXPConnectWrappedNative. + * A new wrapper is *never* constructed. + */ + nsresult GetWrappedNativeOfJSObject(JSContext* aJSContext, JSObject* aJSObj, + nsIXPConnectWrappedNative** _retval); + + nsresult DebugDump(int16_t depth); + nsresult DebugDumpObject(nsISupports* aCOMObj, int16_t depth); + nsresult DebugDumpJSStack(bool showArgs, bool showLocals, bool showThisProps); + + /** + * wrapJSAggregatedToNative is just like wrapJS except it is used in cases + * where the JSObject is also aggregated to some native xpcom Object. + * At present XBL is the only system that might want to do this. + * + * XXX write more! + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_JS + * NS_ERROR_FAILURE + */ + nsresult WrapJSAggregatedToNative(nsISupports* aOuter, JSContext* aJSContext, + JSObject* aJSObj, const nsIID& aIID, + void** result); + + // Methods added since mozilla 0.6.... + + nsresult VariantToJS(JSContext* ctx, JSObject* scope, nsIVariant* value, + JS::MutableHandle<JS::Value> _retval); + nsresult JSToVariant(JSContext* ctx, JS::Handle<JS::Value> value, + nsIVariant** _retval); + + /** + * Create a sandbox for evaluating code in isolation using + * evalInSandboxObject(). + * + * @param cx A context to use when creating the sandbox object. + * @param principal The principal (or NULL to use the null principal) + * to use when evaluating code in this sandbox. + */ + nsresult CreateSandbox(JSContext* cx, nsIPrincipal* principal, + JSObject** _retval); + + /** + * Evaluate script in a sandbox, completely isolated from all + * other running scripts. + * + * @param source The source of the script to evaluate. + * @param filename The filename of the script. May be null. + * @param cx The context to use when setting up the evaluation of + * the script. The actual evaluation will happen on a new + * temporary context. + * @param sandbox The sandbox object to evaluate the script in. + * @return The result of the evaluation as a jsval. If the caller + * intends to use the return value from this call the caller + * is responsible for rooting the jsval before making a call + * to this method. + */ + nsresult EvalInSandboxObject(const nsAString& source, const char* filename, + JSContext* cx, JSObject* sandboxArg, + JS::MutableHandle<JS::Value> rval); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPConnect, NS_IXPCONNECT_IID) + +#endif // defined nsIXPConnect_h diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp new file mode 100644 index 0000000000..f1ee189f0b --- /dev/null +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -0,0 +1,1160 @@ +/* -*- 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 "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 "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. :-( + // + // Bug 1650075: These should really pass GCOptions::Shutdown but doing that + // 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 + 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); + } + + 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 { + 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, + bool aIsSystemPrincipal, + bool aShouldResistFingerprinting) { + bool shouldDiscardSystemSource = ShouldDiscardSystemSource(); + + 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().setShouldResistFingerprinting( + aShouldResistFingerprinting); + + if (shouldDiscardSystemSource) { + aOptions.behaviors().setDiscardSource(aIsSystemPrincipal); + } +} + +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()); + + InitGlobalObjectOptions(aOptions, /* aSystemPrincipal */ true, + /* aShouldResistFingerprinting */ 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, 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); + } + 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_GetFunctionDisplayId(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 { + filenameStr = "x-bogus://XPConnect/Sandbox"_ns; + } + 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 + // 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(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)); +} diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg new file mode 100644 index 0000000000..473106d185 --- /dev/null +++ b/js/xpconnect/src/xpc.msg @@ -0,0 +1,255 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* Error Message definitions. */ + + +/* xpconnect specific codes (from nsIXPConnect.h) */ + +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_ARGS , "Not enough arguments") +XPC_MSG_DEF(NS_ERROR_XPC_NEED_OUT_OBJECT , "'Out' argument must be an object") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_OUT_VAL , "Cannot set 'value' property of 'out' argument") +XPC_MSG_DEF(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE , "Component returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_INTERFACE_INFO , "Cannot find interface information") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO , "Cannot find interface information for parameter") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_METHOD_INFO , "Cannot find method information") +XPC_MSG_DEF(NS_ERROR_XPC_UNEXPECTED , "Unexpected error in XPConnect") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS , "Could not convert JavaScript argument") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_NATIVE , "Could not convert Native argument") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF , "Could not convert JavaScript argument (NULL value cannot be used for a C++ reference type)") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO , "Illegal operation on WrappedNative prototype object") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN , "Cannot convert WrappedNative to function") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN , "Cannot define new property in a WrappedNative") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_WATCH_WN_STATIC , "Cannot place watchpoints on WrappedNative object static properties") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_EXPORT_WN_STATIC , "Cannot export a WrappedNative object's static properties") +XPC_MSG_DEF(NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED , "nsIXPCScriptable::Call failed") +XPC_MSG_DEF(NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED , "nsIXPCScriptable::Construct failed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE , "Cannot use wrapper as function unless it implements nsIXPCScriptable") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE , "Cannot use wrapper as constructor unless it implements nsIXPCScriptable") +XPC_MSG_DEF(NS_ERROR_XPC_CI_RETURNED_FAILURE , "ComponentManager::CreateInstance returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_GS_RETURNED_FAILURE , "ServiceManager::GetService returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CID , "Invalid ClassID or ContractID") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_IID , "Invalid InterfaceID") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CREATE_WN , "Cannot create wrapper around native interface") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_EXCEPTION , "JavaScript component threw exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT , "JavaScript component threw a native object that is not an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_JS_OBJECT , "JavaScript component threw a JavaScript object") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NULL , "JavaScript component threw a null value as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_STRING , "JavaScript component threw a string as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NUMBER , "JavaScript component threw a number as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JAVASCRIPT_ERROR , "JavaScript component caused a JavaScript error") +XPC_MSG_DEF(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS , "JavaScript component caused a JavaScript error (detailed report attached)") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY, "Cannot convert primitive JavaScript value into an array") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY , "Cannot convert JavaScript object into an array") +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY , "JavaScript Array does not have as many elements as indicated by size argument") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_ARRAY_INFO , "Cannot find array information") +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING , "JavaScript String does not have as many characters as indicated by size argument") +XPC_MSG_DEF(NS_ERROR_XPC_SECURITY_MANAGER_VETO , "Security Manager vetoed action") +XPC_MSG_DEF(NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE , "Failed to build a wrapper because the interface that was not declared [scriptable]") +XPC_MSG_DEF(NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS , "Failed to build a wrapper because the interface does not inherit from nsISupports") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT , "Property is a constant and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE , "Property is a read only attribute and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD , "Property is an interface method and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE, "Cannot add property to WrappedNative object") +XPC_MSG_DEF(NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED , "Call to nsIXPCScriptable interface for WrappedNative failed unexpecedly") +XPC_MSG_DEF(NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED , "JavaScript component does not have a method named:") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_ID_STRING , "Bad ID string") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_INITIALIZER_NAME , "Bad initializer name in Constructor - Component has no method with that name") +XPC_MSG_DEF(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN , "Operation failed because the XPConnect subsystem has been shutdown") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN , "Cannot modify properties of a WrappedNative") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL , "Could not convert JavaScript argument - 0 was passed, expected object. Did you mean null?") + + +/* common global codes (from nsError.h) */ + +XPC_MSG_DEF(NS_OK , "Success") +XPC_MSG_DEF(NS_ERROR_NOT_INITIALIZED , "Component not initialized") +XPC_MSG_DEF(NS_ERROR_ALREADY_INITIALIZED , "Component already initialized") +XPC_MSG_DEF(NS_ERROR_NOT_IMPLEMENTED , "Method not implemented") +XPC_MSG_DEF(NS_NOINTERFACE , "Component does not have requested interface") +XPC_MSG_DEF(NS_ERROR_NO_INTERFACE , "Component does not have requested interface") +XPC_MSG_DEF(NS_ERROR_ILLEGAL_VALUE , "Illegal value") +XPC_MSG_DEF(NS_ERROR_INVALID_POINTER , "Invalid pointer") +XPC_MSG_DEF(NS_ERROR_NULL_POINTER , "Null pointer") +XPC_MSG_DEF(NS_ERROR_ABORT , "Abort") +XPC_MSG_DEF(NS_ERROR_FAILURE , "Failure") +XPC_MSG_DEF(NS_ERROR_UNEXPECTED , "Unexpected error") +XPC_MSG_DEF(NS_ERROR_OUT_OF_MEMORY , "Out of Memory") +XPC_MSG_DEF(NS_ERROR_INVALID_ARG , "Invalid argument") +XPC_MSG_DEF(NS_ERROR_NOT_AVAILABLE , "Component is not available") +XPC_MSG_DEF(NS_ERROR_FACTORY_NOT_REGISTERED , "Factory not registered") +XPC_MSG_DEF(NS_ERROR_FACTORY_REGISTER_AGAIN , "Factory not registered (may be tried again)") +XPC_MSG_DEF(NS_ERROR_FACTORY_NOT_LOADED , "Factory not loaded") +XPC_MSG_DEF(NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT , "Factory does not support signatures") +XPC_MSG_DEF(NS_ERROR_FACTORY_EXISTS , "Factory already exists") + +/* added from nsError.h on Feb 28 2001... */ + +XPC_MSG_DEF(NS_BASE_STREAM_CLOSED , "Stream closed") +XPC_MSG_DEF(NS_BASE_STREAM_OSERROR , "Error from the operating system") +XPC_MSG_DEF(NS_BASE_STREAM_ILLEGAL_ARGS , "Illegal arguments") +XPC_MSG_DEF(NS_BASE_STREAM_NO_CONVERTER , "No converter for unichar streams") +XPC_MSG_DEF(NS_BASE_STREAM_BAD_CONVERSION , "Bad converter for unichar streams") +XPC_MSG_DEF(NS_BASE_STREAM_WOULD_BLOCK , "Stream would block") + +XPC_MSG_DEF(NS_ERROR_FILE_UNRECOGNIZED_PATH , "File error: Unrecognized path") +XPC_MSG_DEF(NS_ERROR_FILE_UNRESOLVABLE_SYMLINK , "File error: Unresolvable symlink") +XPC_MSG_DEF(NS_ERROR_FILE_EXECUTION_FAILED , "File error: Execution failed") +XPC_MSG_DEF(NS_ERROR_FILE_UNKNOWN_TYPE , "File error: Unknown type") +XPC_MSG_DEF(NS_ERROR_FILE_DESTINATION_NOT_DIR , "File error: Destination not dir") +XPC_MSG_DEF(NS_ERROR_FILE_COPY_OR_MOVE_FAILED , "File error: Copy or move failed") +XPC_MSG_DEF(NS_ERROR_FILE_ALREADY_EXISTS , "File error: Already exists") +XPC_MSG_DEF(NS_ERROR_FILE_INVALID_PATH , "File error: Invalid path") +XPC_MSG_DEF(NS_ERROR_FILE_CORRUPTED , "File error: Corrupted") +XPC_MSG_DEF(NS_ERROR_FILE_NOT_DIRECTORY , "File error: Not directory") +XPC_MSG_DEF(NS_ERROR_FILE_IS_DIRECTORY , "File error: Is directory") +XPC_MSG_DEF(NS_ERROR_FILE_IS_LOCKED , "File error: Is locked") +XPC_MSG_DEF(NS_ERROR_FILE_TOO_BIG , "File error: Too big") +XPC_MSG_DEF(NS_ERROR_FILE_NO_DEVICE_SPACE , "File error: No device space") +XPC_MSG_DEF(NS_ERROR_FILE_NAME_TOO_LONG , "File error: Name too long") +XPC_MSG_DEF(NS_ERROR_FILE_NOT_FOUND , "File error: Not found") +XPC_MSG_DEF(NS_ERROR_FILE_READ_ONLY , "File error: Read only") +XPC_MSG_DEF(NS_ERROR_FILE_DIR_NOT_EMPTY , "File error: Dir not empty") +XPC_MSG_DEF(NS_ERROR_FILE_ACCESS_DENIED , "File error: Access denied") + +/* added from nsError.h on Sept 6 2001... */ + +XPC_MSG_DEF(NS_ERROR_CANNOT_CONVERT_DATA , "Data conversion error") +XPC_MSG_DEF(NS_ERROR_OBJECT_IS_IMMUTABLE , "Can not modify immutable data container") +XPC_MSG_DEF(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA , "Data conversion failed because significant data would be lost") +XPC_MSG_DEF(NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA , "Data conversion succeeded but data was rounded to fit") + +/* network related codes (from nsNetError.h) */ + +XPC_MSG_DEF(NS_BINDING_FAILED , "The async request failed for some unknown reason") +XPC_MSG_DEF(NS_BINDING_ABORTED , "The async request failed because it was aborted by some user action") +XPC_MSG_DEF(NS_BINDING_REDIRECTED , "The async request has been redirected to a different async request") +XPC_MSG_DEF(NS_BINDING_RETARGETED , "The async request has been retargeted to a different handler") +XPC_MSG_DEF(NS_ERROR_MALFORMED_URI , "The URI is malformed") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROTOCOL , "The URI scheme corresponds to an unknown protocol handler") +XPC_MSG_DEF(NS_ERROR_NO_CONTENT , "Channel opened successfully but no data will be returned") +XPC_MSG_DEF(NS_ERROR_IN_PROGRESS , "The requested action could not be completed while the object is busy") +XPC_MSG_DEF(NS_ERROR_ALREADY_OPENED , "Channel is already open") +XPC_MSG_DEF(NS_ERROR_INVALID_CONTENT_ENCODING , "The content encoding of the source document is incorrect") +XPC_MSG_DEF(NS_ERROR_CORRUPTED_CONTENT , "Corrupted content received from server (potentially MIME type mismatch because of 'X-Content-Type-Options: nosniff')") +XPC_MSG_DEF(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, "Couldn't extract first component from potentially corrupted header field") +XPC_MSG_DEF(NS_ERROR_ALREADY_CONNECTED , "The connection is already established") +XPC_MSG_DEF(NS_ERROR_NOT_CONNECTED , "The connection does not exist") +XPC_MSG_DEF(NS_ERROR_CONNECTION_REFUSED , "The connection was refused") + +/* Error codes return from the proxy */ +XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED , "The connection to the proxy server was refused") +XPC_MSG_DEF(NS_ERROR_PROXY_AUTHENTICATION_FAILED , "The proxy requires authentication") +XPC_MSG_DEF(NS_ERROR_PROXY_BAD_GATEWAY , "The request failed on the proxy") +XPC_MSG_DEF(NS_ERROR_PROXY_GATEWAY_TIMEOUT , "The request timed out on the proxy") +XPC_MSG_DEF(NS_ERROR_PROXY_TOO_MANY_REQUESTS , "Sending too many requests to a proxy") +XPC_MSG_DEF(NS_ERROR_PROXY_VERSION_NOT_SUPPORTED , "The proxy does not support the version of the HTTP request") +XPC_MSG_DEF(NS_ERROR_PROXY_FORBIDDEN , "The user is banned from the proxy") +XPC_MSG_DEF(NS_ERROR_PROXY_SERVICE_UNAVAILABLE , "The proxy is not available") +XPC_MSG_DEF(NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS, "The desired destination is unavailable for legal reasons") + +XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT , "The connection has timed out") +XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT_EXTERNAL , "The request has been cancelled because of a timeout") +XPC_MSG_DEF(NS_ERROR_OFFLINE , "The requested action could not be completed in the offline state") +XPC_MSG_DEF(NS_ERROR_PORT_ACCESS_NOT_ALLOWED , "Establishing a connection to an unsafe or otherwise banned port was prohibited") +XPC_MSG_DEF(NS_ERROR_NET_RESET , "The connection was established, but no data was ever received") +XPC_MSG_DEF(NS_ERROR_NET_INTERRUPT , "The connection was established, but the data transfer was interrupted") +XPC_MSG_DEF(NS_ERROR_NET_PARTIAL_TRANSFER , "A transfer was only partially done when it completed") +XPC_MSG_DEF(NS_ERROR_NET_HTTP3_PROTOCOL_ERROR , "There has been a http3 protocol error") +XPC_MSG_DEF(NS_ERROR_NOT_RESUMABLE , "This request is not resumable, but it was tried to resume it, or to request resume-specific data") +XPC_MSG_DEF(NS_ERROR_ENTITY_CHANGED , "It was attempted to resume the request, but the entity has changed in the meantime") +XPC_MSG_DEF(NS_ERROR_REDIRECT_LOOP , "The request failed as a result of a detected redirection loop") +XPC_MSG_DEF(NS_ERROR_UNSAFE_CONTENT_TYPE , "The request failed because the content type returned by the server was not a type expected by the channel") +XPC_MSG_DEF(NS_ERROR_LOAD_SHOWED_ERRORPAGE , "The load caused an error page to be displayed.") +XPC_MSG_DEF(NS_ERROR_BLOCKED_BY_POLICY , "The request was blocked by a policy set by the system administrator.") + +XPC_MSG_DEF(NS_ERROR_UNKNOWN_HOST , "The lookup of the hostname failed") +XPC_MSG_DEF(NS_ERROR_DNS_LOOKUP_QUEUE_FULL , "The DNS lookup queue is full") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROXY_HOST , "The lookup of the proxy hostname failed") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_SOCKET_TYPE , "The specified socket type does not exist") +XPC_MSG_DEF(NS_ERROR_SOCKET_CREATE_FAILED , "The specified socket type could not be created") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED , "The specified socket address type is not supported") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_IN_USE , "Some other socket is already using the specified address.") +XPC_MSG_DEF(NS_ERROR_CACHE_KEY_NOT_FOUND , "Cache key could not be found") +XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_STREAM , "Cache data is a stream") +XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_NOT_STREAM , "Cache data is not a stream") +XPC_MSG_DEF(NS_ERROR_CACHE_WAIT_FOR_VALIDATION , "Cache entry exists but needs to be validated first") +XPC_MSG_DEF(NS_ERROR_CACHE_ENTRY_DOOMED , "Cache entry has been doomed") +XPC_MSG_DEF(NS_ERROR_CACHE_READ_ACCESS_DENIED , "Read access to cache denied") +XPC_MSG_DEF(NS_ERROR_CACHE_WRITE_ACCESS_DENIED , "Write access to cache denied") +XPC_MSG_DEF(NS_ERROR_CACHE_IN_USE , "Cache is currently in use") +XPC_MSG_DEF(NS_ERROR_DOCUMENT_NOT_CACHED , "Document does not exist in cache") +XPC_MSG_DEF(NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS , "The requested number of domain levels exceeds those present in the host string") +XPC_MSG_DEF(NS_ERROR_HOST_IS_IP_ADDRESS , "The host string is an IP address") +XPC_MSG_DEF(NS_ERROR_NOT_SAME_THREAD , "Can't access a wrapped JS object from a different thread") + +/* storage related codes (from mozStorage.h) */ +XPC_MSG_DEF(NS_ERROR_STORAGE_BUSY , "SQLite database connection is busy") +XPC_MSG_DEF(NS_ERROR_STORAGE_IOERR , "SQLite encountered an IO error") +XPC_MSG_DEF(NS_ERROR_STORAGE_CONSTRAINT , "SQLite database operation failed because a constraint was violated") + +/* plugin related codes (from nsPluginError.h) */ +XPC_MSG_DEF(NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, "Clearing site data by time range not supported by plugin") + +/* character converter related codes */ +XPC_MSG_DEF(NS_ERROR_ILLEGAL_INPUT , "The input characters have illegal sequences") + +/* Codes related to signd jars */ +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_NOT_SIGNED , "The JAR is not signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY , "An entry in the JAR has been modified after the JAR was signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY , "An entry in the JAR has not been signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_MISSING , "An entry is missing from the JAR file.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE , "The JAR's signature is wrong.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is too large.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.") +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO , "The PKCS#7 signature is malformed or invalid.") +XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_SIGNED , "The PKCS#7 information is not signed.") + +/* Codes related to signed manifests */ +XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.") + +/* Codes for printing-related errors. */ +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE , "No printers available.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND , "The selected printer could not be found.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE , "Failed to open output file for print to file.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTDOC , "Printing failed while starting the print job.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_ENDDOC , "Printing failed while completing the print job.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTPAGE , "Printing failed while starting a new page.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY , "Cannot print this document yet, it is still being loaded.") + +/* Codes related to content */ +XPC_MSG_DEF(NS_ERROR_CONTENT_CRASHED , "The process that hosted this content has crashed.") +XPC_MSG_DEF(NS_ERROR_FRAME_CRASHED , "The process that hosted this frame has crashed.") +XPC_MSG_DEF(NS_ERROR_BUILDID_MISMATCH , "The process that hosted this content did not have the same buildID as the parent.") +XPC_MSG_DEF(NS_ERROR_CONTENT_BLOCKED , "The load for this content was blocked.") + +/* Codes for the JS-implemented Push DOM API. These can be removed as part of bug 1252660. */ +XPC_MSG_DEF(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR , "Invalid raw ECDSA P-256 public key.") +XPC_MSG_DEF(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR , "A subscription with a different application server key already exists.") + +/* Codes defined in WebIDL https://heycam.github.io/webidl/#idl-DOMException-error-names */ +XPC_MSG_DEF(NS_ERROR_DOM_NOT_FOUND_ERR , "The object can not be found here.") +XPC_MSG_DEF(NS_ERROR_DOM_NOT_ALLOWED_ERR , "The request is not allowed.") + +/* Codes related to the URIClassifier service */ +XPC_MSG_DEF(NS_ERROR_MALWARE_URI , "The URI is malware") +XPC_MSG_DEF(NS_ERROR_PHISHING_URI , "The URI is phishing") +XPC_MSG_DEF(NS_ERROR_TRACKING_URI , "The URI is tracking") +XPC_MSG_DEF(NS_ERROR_UNWANTED_URI , "The URI is unwanted") +XPC_MSG_DEF(NS_ERROR_BLOCKED_URI , "The URI is blocked") +XPC_MSG_DEF(NS_ERROR_HARMFUL_URI , "The URI is harmful") +XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinting") +XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining") +XPC_MSG_DEF(NS_ERROR_SOCIALTRACKING_URI , "The URI is social tracking") +XPC_MSG_DEF(NS_ERROR_EMAILTRACKING_URI , "The URI is email tracking") + +/* Profile manager error codes */ +XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.") + +/* Codes related to URILoader */ +XPC_MSG_DEF(NS_ERROR_PARSED_DATA_CACHED , "The data from a channel has already been parsed and cached so it doesn't need to be reparsed from the original source.") +XPC_MSG_DEF(NS_BINDING_CANCELLED_OLD_LOAD , "The async request has been cancelled by another async request") diff --git a/js/xpconnect/src/xpcObjectHelper.h b/js/xpconnect/src/xpcObjectHelper.h new file mode 100644 index 0000000000..1d83fdde15 --- /dev/null +++ b/js/xpconnect/src/xpcObjectHelper.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef xpcObjectHelper_h +#define xpcObjectHelper_h + +// Including 'windows.h' will #define GetClassInfo to something else. +#ifdef XP_WIN +# ifdef GetClassInfo +# undef GetClassInfo +# endif +#endif + +#include "mozilla/Attributes.h" +#include <stdint.h> +#include "nsCOMPtr.h" +#include "nsIClassInfo.h" +#include "nsISupports.h" +#include "nsIXPCScriptable.h" +#include "nsWrapperCache.h" + +class xpcObjectHelper { + public: + explicit xpcObjectHelper(nsISupports* aObject, + nsWrapperCache* aCache = nullptr) + : mObject(aObject), mCache(aCache) { + if (!mCache && aObject) { + CallQueryInterface(aObject, &mCache); + } + } + + nsISupports* Object() { return mObject; } + + nsIClassInfo* GetClassInfo() { + if (!mClassInfo) { + mClassInfo = do_QueryInterface(mObject); + } + return mClassInfo; + } + + // We assert that we can reach an nsIXPCScriptable somehow. + uint32_t GetScriptableFlags() { + nsCOMPtr<nsIXPCScriptable> sinfo = do_QueryInterface(mObject); + + // We should have something by now. + MOZ_ASSERT(sinfo); + + // Grab the flags. + return sinfo->GetScriptableFlags(); + } + + nsWrapperCache* GetWrapperCache() { return mCache; } + + private: + xpcObjectHelper(xpcObjectHelper& aOther) = delete; + + nsISupports* MOZ_UNSAFE_REF( + "xpcObjectHelper has been specifically optimized " + "to avoid unnecessary AddRefs and Releases. " + "(see bug 565742)") mObject; + nsWrapperCache* mCache; + nsCOMPtr<nsIClassInfo> mClassInfo; +}; + +#endif diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h new file mode 100644 index 0000000000..2c629e5bc2 --- /dev/null +++ b/js/xpconnect/src/xpcprivate.h @@ -0,0 +1,2842 @@ +/* -*- 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/. */ + +/* + * XPConnect allows JS code to manipulate C++ object and C++ code to manipulate + * JS objects. JS manipulation of C++ objects tends to be significantly more + * complex. This comment explains how it is orchestrated by XPConnect. + * + * For each C++ object to be manipulated in JS, there is a corresponding JS + * object. This is called the "flattened JS object". By default, there is an + * additional C++ object involved of type XPCWrappedNative. The XPCWrappedNative + * holds pointers to the C++ object and the flat JS object. + * + * All XPCWrappedNative objects belong to an XPCWrappedNativeScope. These scopes + * are essentially in 1:1 correspondence with JS compartments. The + * XPCWrappedNativeScope has a pointer to the JS compartment. The global of a + * flattened JS object is one of the globals in this compartment (the exception + * to this rule is when a PreCreate hook asks for a different global; see + * nsIXPCScriptable below). + * + * Some C++ objects (notably DOM objects) have information associated with them + * that lists the interfaces implemented by these objects. A C++ object exposes + * this information by implementing nsIClassInfo. If a C++ object implements + * nsIClassInfo, then JS code can call its methods without needing to use + * QueryInterface first. Typically, all instances of a C++ class share the same + * nsIClassInfo instance. (That is, obj->QueryInterface(nsIClassInfo) returns + * the same result for every obj of a given class.) + * + * XPConnect tracks nsIClassInfo information in an XPCWrappedNativeProto object. + * A given XPCWrappedNativeScope will have one XPCWrappedNativeProto for each + * nsIClassInfo instance being used. The XPCWrappedNativeProto has an associated + * JS object, which is used as the prototype of all flattened JS objects created + * for C++ objects with the given nsIClassInfo. + * + * Each XPCWrappedNativeProto has a pointer to its XPCWrappedNativeScope. If an + * XPCWrappedNative wraps a C++ object with class info, then it points to its + * XPCWrappedNativeProto. Otherwise it points to its XPCWrappedNativeScope. (The + * pointers are smooshed together in a tagged union.) Either way it can reach + * its scope. + * + * An XPCWrappedNativeProto keeps track of the set of interfaces implemented by + * the C++ object in an XPCNativeSet. (The list of interfaces is obtained by + * calling a method on the nsIClassInfo.) An XPCNativeSet is a collection of + * XPCNativeInterfaces. Each interface stores the list of members, which can be + * methods, constants, getters, or setters. + * + * An XPCWrappedNative also points to an XPCNativeSet. Initially this starts out + * the same as the XPCWrappedNativeProto's set. If there is no proto, it starts + * out as a singleton set containing nsISupports. If JS code QI's new interfaces + * outside of the existing set, the set will grow. All QueryInterface results + * are cached in XPCWrappedNativeTearOff objects, which are linked off of the + * XPCWrappedNative. + * + * Besides having class info, a C++ object may be "scriptable" (i.e., implement + * nsIXPCScriptable). This allows it to implement a more DOM-like interface, + * besides just exposing XPCOM methods and constants. An nsIXPCScriptable + * instance has hooks that correspond to all the normal JSClass hooks. Each + * nsIXPCScriptable instance can have pointers from XPCWrappedNativeProto and + * XPCWrappedNative (since C++ objects can have scriptable info without having + * class info). + */ + +/* All the XPConnect private declarations - only include locally. */ + +#ifndef xpcprivate_h___ +#define xpcprivate_h___ + +#include "mozilla/Alignment.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/DefineEnum.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/mozalloc.h" +#include "mozilla/Preferences.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +#include "mozilla/dom/ScriptSettings.h" + +#include <math.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "xpcpublic.h" +#include "js/HashTable.h" +#include "js/GCHashTable.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment +#include "js/PropertyAndElement.h" // JS_DefineProperty +#include "js/TracingAPI.h" +#include "js/WeakMapPtr.h" +#include "nscore.h" +#include "nsXPCOM.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsIServiceManager.h" +#include "nsIClassInfoImpl.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsISupportsPrimitives.h" +#include "nsISimpleEnumerator.h" +#include "nsIXPConnect.h" +#include "nsIXPCScriptable.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsXPTCUtils.h" +#include "xptinfo.h" +#include "XPCForwards.h" +#include "XPCLog.h" +#include "xpccomponents.h" +#include "prenv.h" +#include "prcvar.h" +#include "nsString.h" +#include "nsReadableUtils.h" + +#include "MainThreadUtils.h" + +#include "nsIConsoleService.h" + +#include "nsVariant.h" +#include "nsCOMArray.h" +#include "nsTArray.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsWrapperCache.h" +#include "nsStringBuffer.h" +#include "nsDeque.h" + +#include "nsIScriptSecurityManager.h" + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsIScriptObjectPrincipal.h" +#include "xpcObjectHelper.h" + +#include "SandboxPrivate.h" +#include "BackstagePass.h" + +#ifdef XP_WIN +// Nasty MS defines +# ifdef GetClassInfo +# undef GetClassInfo +# endif +# ifdef GetClassName +# undef GetClassName +# endif +#endif /* XP_WIN */ + +namespace mozilla { +namespace dom { +class AutoEntryScript; +class Exception; +} // namespace dom +} // namespace mozilla + +/***************************************************************************/ +// data declarations... +extern const char XPC_EXCEPTION_CONTRACTID[]; +extern const char XPC_CONSOLE_CONTRACTID[]; +extern const char XPC_SCRIPT_ERROR_CONTRACTID[]; +extern const char XPC_XPCONNECT_CONTRACTID[]; + +/***************************************************************************/ +// Helper function. + +namespace xpc { + +inline bool IsWrappedNativeReflector(JSObject* obj) { + return JS::GetClass(obj)->isWrappedNative(); +} + +} // namespace xpc + +/*************************************************************************** +**************************************************************************** +* +* Core runtime and context classes... +* +**************************************************************************** +***************************************************************************/ + +// We have a general rule internally that getters that return addref'd interface +// pointer generally do so using an 'out' parm. When interface pointers are +// returned as function call result values they are not addref'd. Exceptions +// to this rule are noted explicitly. + +class nsXPConnect final : public nsIXPConnect { + public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + + // non-interface implementation + public: + static XPCJSRuntime* GetRuntimeInstance(); + XPCJSContext* GetContext() { return mContext; } + + static nsIScriptSecurityManager* SecurityManager() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gScriptSecurityManager); + return gScriptSecurityManager; + } + + static nsIPrincipal* SystemPrincipal() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gSystemPrincipal); + return gSystemPrincipal; + } + + // Called by module code in dll startup + static void InitStatics(); + // Called by module code on dll shutdown. + static void ReleaseXPConnectSingleton(); + + static void InitJSContext(); + + void RecordTraversal(void* p, nsISupports* s); + + protected: + virtual ~nsXPConnect(); + + nsXPConnect(); + + private: + // Singleton instance + static nsXPConnect* gSelf; + static bool gOnceAliveNowDead; + + XPCJSContext* mContext = nullptr; + XPCJSRuntime* mRuntime = nullptr; + + friend class nsIXPConnect; + + public: + static nsIScriptSecurityManager* gScriptSecurityManager; + static nsIPrincipal* gSystemPrincipal; +}; + +/***************************************************************************/ + +// In the current xpconnect system there can only be one XPCJSContext. +// So, xpconnect can only be used on one JSContext within the process. + +class WatchdogManager; + +// clang-format off +MOZ_DEFINE_ENUM(WatchdogTimestampCategory, ( + TimestampWatchdogWakeup, + TimestampWatchdogHibernateStart, + TimestampWatchdogHibernateStop, + TimestampContextStateChange +)); +// clang-format on + +class AsyncFreeSnowWhite; +class XPCWrappedNativeScope; + +using XPCWrappedNativeScopeList = mozilla::LinkedList<XPCWrappedNativeScope>; + +class XPCJSContext final : public mozilla::CycleCollectedJSContext, + public mozilla::LinkedListElement<XPCJSContext> { + public: + static XPCJSContext* NewXPCJSContext(); + static XPCJSContext* Get(); + + XPCJSRuntime* Runtime() const; + + virtual mozilla::CycleCollectedJSRuntime* CreateRuntime( + JSContext* aCx) override; + + XPCCallContext* GetCallContext() const { return mCallContext; } + XPCCallContext* SetCallContext(XPCCallContext* ccx) { + XPCCallContext* old = mCallContext; + mCallContext = ccx; + return old; + } + + jsid GetResolveName() const { return mResolveName; } + jsid SetResolveName(jsid name) { + jsid old = mResolveName; + mResolveName = name; + return old; + } + + XPCWrappedNative* GetResolvingWrapper() const { return mResolvingWrapper; } + XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w) { + XPCWrappedNative* old = mResolvingWrapper; + mResolvingWrapper = w; + return old; + } + + bool JSContextInitialized(JSContext* cx); + + virtual void BeforeProcessTask(bool aMightBlock) override; + virtual void AfterProcessTask(uint32_t aNewRecursionDepth) override; + + // Relay to the CCGCScheduler instead of queuing up an idle runnable + // (as is done for workers in CycleCollectedJSContext). + virtual void MaybePokeGC() override; + + ~XPCJSContext(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + bool IsSystemCaller() const override; + + AutoMarkingPtr** GetAutoRootsAdr() { return &mAutoRoots; } + + nsresult GetPendingResult() { return mPendingResult; } + void SetPendingResult(nsresult rv) { mPendingResult = rv; } + + PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory); + + static bool RecordScriptActivity(bool aActive); + + bool SetHasScriptActivity(bool aActive) { + bool oldValue = mHasScriptActivity; + mHasScriptActivity = aActive; + return oldValue; + } + + static bool InterruptCallback(JSContext* cx); + + // Mapping of often used strings to jsid atoms that live 'forever'. + // + // To add a new string: add to this list and to XPCJSRuntime::mStrings + // at the top of XPCJSRuntime.cpp + enum { + IDX_CONSTRUCTOR = 0, + IDX_TO_STRING, + IDX_TO_SOURCE, + IDX_VALUE, + IDX_QUERY_INTERFACE, + IDX_COMPONENTS, + IDX_CC, + IDX_CI, + IDX_CR, + IDX_CU, + IDX_SERVICES, + IDX_WRAPPED_JSOBJECT, + IDX_PROTOTYPE, + IDX_EVAL, + IDX_CONTROLLERS, + IDX_CONTROLLERS_CLASS, + IDX_LENGTH, + IDX_NAME, + IDX_UNDEFINED, + IDX_EMPTYSTRING, + IDX_FILENAME, + IDX_LINENUMBER, + IDX_COLUMNNUMBER, + IDX_STACK, + IDX_MESSAGE, + IDX_CAUSE, + IDX_ERRORS, + IDX_LASTINDEX, + IDX_THEN, + IDX_ISINSTANCE, + IDX_INFINITY, + IDX_NAN, + IDX_CLASS_ID, + IDX_INTERFACE_ID, + IDX_INITIALIZER, + IDX_PRINT, + IDX_FETCH, + IDX_CRYPTO, + IDX_INDEXEDDB, + IDX_STRUCTUREDCLONE, + IDX_TOTAL_COUNT // just a count of the above + }; + + inline JS::HandleId GetStringID(unsigned index) const; + inline const char* GetStringName(unsigned index) const; + + private: + XPCJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(); + + XPCCallContext* mCallContext; + AutoMarkingPtr* mAutoRoots; + jsid mResolveName; + XPCWrappedNative* mResolvingWrapper; + WatchdogManager* mWatchdogManager; + + // Number of XPCJSContexts currently alive. + static uint32_t sInstanceCount; + static mozilla::StaticAutoPtr<WatchdogManager> sWatchdogInstance; + static WatchdogManager* GetWatchdogManager(); + + // If we spend too much time running JS code in an event handler, then we + // want to show the slow script UI. The timeout T is controlled by prefs. We + // invoke the interrupt callback once after T/2 seconds and set + // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the + // interrupt callback again. Since mSlowScriptSecondHalf is now true, it + // shows the slow script UI. The reason we invoke the callback twice is to + // ensure that putting the computer to sleep while running a script doesn't + // cause the UI to be shown. If the laptop goes to sleep during one of the + // timeout periods, the script still has the other T/2 seconds to complete + // before the slow script UI is shown. + bool mSlowScriptSecondHalf; + + // mSlowScriptCheckpoint is set to the time when: + // 1. We started processing the current event, or + // 2. mSlowScriptSecondHalf was set to true + // (whichever comes later). We use it to determine whether the interrupt + // callback needs to do anything. + mozilla::TimeStamp mSlowScriptCheckpoint; + // Accumulates total time we actually waited for telemetry + mozilla::TimeDuration mSlowScriptActualWait; + bool mTimeoutAccumulated; + bool mExecutedChromeScript; + + bool mHasScriptActivity; + + // mPendingResult is used to implement Components.returnCode. Only really + // meaningful while calling through XPCWrappedJS. + nsresult mPendingResult; + + // These members must be accessed via WatchdogManager. + enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mActive; + PRTime mLastStateChange; + + friend class XPCJSRuntime; + friend class Watchdog; + friend class WatchdogManager; + friend class AutoLockWatchdog; +}; + +class XPCJSRuntime final : public mozilla::CycleCollectedJSRuntime { + public: + static XPCJSRuntime* Get(); + + void RemoveWrappedJS(nsXPCWrappedJS* wrapper); + void AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const; + + JSObject2WrappedJSMap* GetMultiCompartmentWrappedJSMap() const { + return mWrappedJSMap.get(); + } + + IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const { + return mIID2NativeInterfaceMap.get(); + } + + ClassInfo2NativeSetMap* GetClassInfo2NativeSetMap() const { + return mClassInfo2NativeSetMap.get(); + } + + NativeSetMap* GetNativeSetMap() const { return mNativeSetMap.get(); } + + using WrappedNativeProtoVector = + mozilla::Vector<mozilla::UniquePtr<XPCWrappedNativeProto>, 0, + InfallibleAllocPolicy>; + WrappedNativeProtoVector& GetDyingWrappedNativeProtos() { + return mDyingWrappedNativeProtos; + } + + XPCWrappedNativeScopeList& GetWrappedNativeScopes() { + return mWrappedNativeScopes; + } + + bool InitializeStrings(JSContext* cx); + + virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp, + char (&aName)[72]) const override; + virtual bool NoteCustomGCThingXPCOMChildren( + const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const override; + + /** + * Infrastructure for classes that need to defer part of the finalization + * until after the GC has run, for example for objects that we don't want to + * destroy during the GC. + */ + + public: + bool GetDoingFinalization() const { return mDoingFinalization; } + + JS::HandleId GetStringID(unsigned index) const { + MOZ_ASSERT(index < XPCJSContext::IDX_TOTAL_COUNT, "index out of range"); + // fromMarkedLocation() is safe because the string is interned. + return JS::HandleId::fromMarkedLocation(&mStrIDs[index]); + } + const char* GetStringName(unsigned index) const { + MOZ_ASSERT(index < XPCJSContext::IDX_TOTAL_COUNT, "index out of range"); + return mStrings[index]; + } + + virtual bool UsefulToMergeZones() const override; + void TraceNativeBlackRoots(JSTracer* trc) override; + void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) override; + void TraverseAdditionalNativeRoots( + nsCycleCollectionNoteRootCallback& cb) override; + void UnmarkSkippableJSHolders(); + void PrepareForForgetSkippable() override; + void BeginCycleCollectionCallback(mozilla::CCReason aReason) override; + void EndCycleCollectionCallback( + mozilla::CycleCollectorResults& aResults) override; + void DispatchDeferredDeletion(bool aContinuation, + bool aPurge = false) override; + + void CustomGCCallback(JSGCStatus status) override; + void CustomOutOfMemoryCallback() override; + void OnLargeAllocationFailure(); + static void GCSliceCallback(JSContext* cx, JS::GCProgress progress, + const JS::GCDescription& desc); + static void DoCycleCollectionCallback(JSContext* cx); + static void FinalizeCallback(JS::GCContext* gcx, JSFinalizeStatus status, + void* data); + static void WeakPointerZonesCallback(JSTracer* trc, void* data); + static void WeakPointerCompartmentCallback(JSTracer* trc, + JS::Compartment* comp, void* data); + + inline void AddSubjectToFinalizationWJS(nsXPCWrappedJS* wrappedJS); + + void DebugDump(int16_t depth); + + bool GCIsRunning() const { return mGCIsRunning; } + + ~XPCJSRuntime(); + + void AddGCCallback(xpcGCCallback cb); + void RemoveGCCallback(xpcGCCallback cb); + + JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + /** + * The unprivileged junk scope is an unprivileged sandbox global used for + * convenience by certain operations which need an unprivileged global but + * don't have one immediately handy. It should generally be avoided when + * possible. + * + * The scope is created lazily when it is needed, and held weakly so that it + * is destroyed when there are no longer any remaining external references to + * it. This means that under low memory conditions, when the scope does not + * already exist, we may not be able to create one. In these circumstances, + * the infallible version of this API will abort, and the fallible version + * will return null. Callers should therefore prefer the fallible version when + * on a codepath which can already return failure, but may use the infallible + * one otherwise. + */ + JSObject* UnprivilegedJunkScope(); + JSObject* UnprivilegedJunkScope(const mozilla::fallible_t&); + + bool IsUnprivilegedJunkScope(JSObject*); + JSObject* LoaderGlobal(); + + void DeleteSingletonScopes(); + + private: + explicit XPCJSRuntime(JSContext* aCx); + + MOZ_IS_CLASS_INIT + void Initialize(JSContext* cx); + void Shutdown(JSContext* cx) override; + + static const char* const mStrings[XPCJSContext::IDX_TOTAL_COUNT]; + jsid mStrIDs[XPCJSContext::IDX_TOTAL_COUNT]; + + struct Hasher { + using Key = RefPtr<mozilla::BasePrincipal>; + using Lookup = Key; + static uint32_t hash(const Lookup& l) { return l->GetOriginNoSuffixHash(); } + static bool match(const Key& k, const Lookup& l) { + return k->FastEquals(l); + } + }; + + struct MapEntryGCPolicy { + static bool traceWeak(JSTracer* trc, + RefPtr<mozilla::BasePrincipal>* /* unused */, + JS::Heap<JSObject*>* value) { + return JS::GCPolicy<JS::Heap<JSObject*>>::traceWeak(trc, value); + } + }; + + typedef JS::GCHashMap<RefPtr<mozilla::BasePrincipal>, JS::Heap<JSObject*>, + Hasher, js::SystemAllocPolicy, MapEntryGCPolicy> + Principal2JSObjectMap; + + mozilla::UniquePtr<JSObject2WrappedJSMap> mWrappedJSMap; + mozilla::UniquePtr<IID2NativeInterfaceMap> mIID2NativeInterfaceMap; + mozilla::UniquePtr<ClassInfo2NativeSetMap> mClassInfo2NativeSetMap; + mozilla::UniquePtr<NativeSetMap> mNativeSetMap; + Principal2JSObjectMap mUAWidgetScopeMap; + XPCWrappedNativeScopeList mWrappedNativeScopes; + WrappedNativeProtoVector mDyingWrappedNativeProtos; + bool mGCIsRunning; + nsTArray<nsISupports*> mNativesToReleaseArray; + bool mDoingFinalization; + mozilla::LinkedList<nsXPCWrappedJS> mSubjectToFinalizationWJS; + nsTArray<xpcGCCallback> extraGCCallbacks; + JS::GCSliceCallback mPrevGCSliceCallback; + JS::DoCycleCollectionCallback mPrevDoCycleCollectionCallback; + mozilla::WeakPtr<SandboxPrivate> mUnprivilegedJunkScope; + JS::PersistentRootedObject mLoaderGlobal; + RefPtr<AsyncFreeSnowWhite> mAsyncSnowWhiteFreer; + + friend class XPCJSContext; + friend class XPCIncrementalReleaseRunnable; +}; + +inline JS::HandleId XPCJSContext::GetStringID(unsigned index) const { + return Runtime()->GetStringID(index); +} + +inline const char* XPCJSContext::GetStringName(unsigned index) const { + return Runtime()->GetStringName(index); +} + +/***************************************************************************/ + +// No virtuals +// XPCCallContext is ALWAYS declared as a local variable in some function; +// i.e. instance lifetime is always controled by some C++ function returning. +// +// These things are created frequently in many places. We *intentionally* do +// not inialialize all members in order to save on construction overhead. +// Some constructor pass more valid params than others. We init what must be +// init'd and leave other members undefined. In debug builds the accessors +// use a CHECK_STATE macro to track whether or not the object is in a valid +// state to answer the question a caller might be asking. As long as this +// class is maintained correctly it can do its job without a bunch of added +// overhead from useless initializations and non-DEBUG error checking. +// +// Note that most accessors are inlined. + +class MOZ_STACK_CLASS XPCCallContext final { + public: + enum : unsigned { NO_ARGS = (unsigned)-1 }; + + explicit XPCCallContext(JSContext* cx, JS::HandleObject obj = nullptr, + JS::HandleObject funobj = nullptr, + JS::HandleId id = JS::VoidHandlePropertyKey, + unsigned argc = NO_ARGS, JS::Value* argv = nullptr, + JS::Value* rval = nullptr); + + virtual ~XPCCallContext(); + + inline bool IsValid() const; + + inline XPCJSContext* GetContext() const; + inline JSContext* GetJSContext() const; + inline bool GetContextPopRequired() const; + inline XPCCallContext* GetPrevCallContext() const; + + inline JSObject* GetFlattenedJSObject() const; + inline XPCWrappedNative* GetWrapper() const; + + inline bool CanGetTearOff() const; + inline XPCWrappedNativeTearOff* GetTearOff() const; + + inline nsIXPCScriptable* GetScriptable() const; + inline XPCNativeSet* GetSet() const; + inline bool CanGetInterface() const; + inline XPCNativeInterface* GetInterface() const; + inline XPCNativeMember* GetMember() const; + inline bool HasInterfaceAndMember() const; + inline bool GetStaticMemberIsLocal() const; + inline unsigned GetArgc() const; + inline JS::Value* GetArgv() const; + + inline uint16_t GetMethodIndex() const; + + inline jsid GetResolveName() const; + inline jsid SetResolveName(JS::HandleId name); + + inline XPCWrappedNative* GetResolvingWrapper() const; + inline XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w); + + inline void SetRetVal(const JS::Value& val); + + void SetName(jsid name); + void SetArgsAndResultPtr(unsigned argc, JS::Value* argv, JS::Value* rval); + void SetCallInfo(XPCNativeInterface* iface, XPCNativeMember* member, + bool isSetter); + + nsresult CanCallNow(); + + void SystemIsBeingShutDown(); + + operator JSContext*() const { return GetJSContext(); } + + private: + // no copy ctor or assignment allowed + XPCCallContext(const XPCCallContext& r) = delete; + XPCCallContext& operator=(const XPCCallContext& r) = delete; + + private: + // posible values for mState + enum State { + INIT_FAILED, + SYSTEM_SHUTDOWN, + HAVE_CONTEXT, + HAVE_OBJECT, + HAVE_NAME, + HAVE_ARGS, + READY_TO_CALL, + CALL_DONE + }; + +#ifdef DEBUG + inline void CHECK_STATE(int s) const { MOZ_ASSERT(mState >= s, "bad state"); } +#else +# define CHECK_STATE(s) ((void)0) +#endif + + private: + State mState; + + nsCOMPtr<nsIXPConnect> mXPC; + + XPCJSContext* mXPCJSContext; + JSContext* mJSContext; + + // ctor does not necessarily init the following. BEWARE! + + XPCCallContext* mPrevCallContext; + + XPCWrappedNative* mWrapper; + XPCWrappedNativeTearOff* mTearOff; + + nsCOMPtr<nsIXPCScriptable> mScriptable; + + RefPtr<XPCNativeSet> mSet; + RefPtr<XPCNativeInterface> mInterface; + XPCNativeMember* mMember; + + JS::RootedId mName; + bool mStaticMemberIsLocal; + + unsigned mArgc; + JS::Value* mArgv; + JS::Value* mRetVal; + + uint16_t mMethodIndex; +}; + +/*************************************************************************** +**************************************************************************** +* +* Core classes for wrapped native objects for use from JavaScript... +* +**************************************************************************** +***************************************************************************/ + +// These are the various JSClasses and callbacks whose use that required +// visibility from more than one .cpp file. + +extern const JSClass XPC_WN_NoHelper_JSClass; +extern const JSClass XPC_WN_Proto_JSClass; +extern const JSClass XPC_WN_Tearoff_JSClass; +extern const JSClass XPC_WN_NoHelper_Proto_JSClass; + +extern bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp); + +extern bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp); + +/***************************************************************************/ +// XPCWrappedNativeScope is one-to-one with a JS compartment. + +class XPCWrappedNativeScope final + : public mozilla::LinkedListElement<XPCWrappedNativeScope> { + public: + XPCJSRuntime* GetRuntime() const { return XPCJSRuntime::Get(); } + + Native2WrappedNativeMap* GetWrappedNativeMap() const { + return mWrappedNativeMap.get(); + } + + ClassInfo2WrappedNativeProtoMap* GetWrappedNativeProtoMap() const { + return mWrappedNativeProtoMap.get(); + } + + nsXPCComponents* GetComponents() const { return mComponents; } + + bool AttachComponentsObject(JSContext* aCx); + + bool AttachJSServices(JSContext* aCx); + + // Returns the JS object reflection of the Components object. + bool GetComponentsJSObject(JSContext* cx, JS::MutableHandleObject obj); + + JSObject* GetExpandoChain(JS::HandleObject target); + + JSObject* DetachExpandoChain(JS::HandleObject target); + + bool SetExpandoChain(JSContext* cx, JS::HandleObject target, + JS::HandleObject chain); + + static void SystemIsBeingShutDown(); + + static void TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt, + JSTracer* trc); + + void TraceInside(JSTracer* trc) { + if (mXrayExpandos.initialized()) { + mXrayExpandos.trace(trc); + } + JS::TraceEdge(trc, &mIDProto, "XPCWrappedNativeScope::mIDProto"); + JS::TraceEdge(trc, &mIIDProto, "XPCWrappedNativeScope::mIIDProto"); + JS::TraceEdge(trc, &mCIDProto, "XPCWrappedNativeScope::mCIDProto"); + } + + static void SuspectAllWrappers(nsCycleCollectionNoteRootCallback& cb); + + static void SweepAllWrappedNativeTearOffs(); + + void UpdateWeakPointersAfterGC(JSTracer* trc); + + static void DebugDumpAllScopes(int16_t depth); + + void DebugDump(int16_t depth); + + struct ScopeSizeInfo { + explicit ScopeSizeInfo(mozilla::MallocSizeOf mallocSizeOf) + : mMallocSizeOf(mallocSizeOf), + mScopeAndMapSize(0), + mProtoAndIfaceCacheSize(0) {} + + mozilla::MallocSizeOf mMallocSizeOf; + size_t mScopeAndMapSize; + size_t mProtoAndIfaceCacheSize; + }; + + static void AddSizeOfAllScopesIncludingThis(JSContext* cx, + ScopeSizeInfo* scopeSizeInfo); + + void AddSizeOfIncludingThis(JSContext* cx, ScopeSizeInfo* scopeSizeInfo); + + // Check whether our mAllowContentXBLScope state matches the given + // principal. This is used to avoid sharing compartments on + // mismatch. + bool XBLScopeStateMatches(nsIPrincipal* aPrincipal); + + XPCWrappedNativeScope(JS::Compartment* aCompartment, + JS::HandleObject aFirstGlobal); + virtual ~XPCWrappedNativeScope(); + + mozilla::UniquePtr<JSObject2JSObjectMap> mWaiverWrapperMap; + + JS::Compartment* Compartment() const { return mCompartment; } + + // Returns the global to use for new WrappedNative objects allocated in this + // compartment. This is better than using arbitrary globals we happen to be in + // because it prevents leaks (objects keep their globals alive). + JSObject* GetGlobalForWrappedNatives() { + return js::GetFirstGlobalInCompartment(Compartment()); + } + + bool AllowContentXBLScope(JS::Realm* aRealm); + + // ID Object prototype caches. + JS::Heap<JSObject*> mIDProto; + JS::Heap<JSObject*> mIIDProto; + JS::Heap<JSObject*> mCIDProto; + + protected: + XPCWrappedNativeScope() = delete; + + private: + mozilla::UniquePtr<Native2WrappedNativeMap> mWrappedNativeMap; + mozilla::UniquePtr<ClassInfo2WrappedNativeProtoMap> mWrappedNativeProtoMap; + RefPtr<nsXPCComponents> mComponents; + JS::Compartment* mCompartment; + + JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos; + + // For remote XUL domains, we run all XBL in the content scope for compat + // reasons (though we sometimes pref this off for automation). We + // track the result of this decision (mAllowContentXBLScope) for now. + bool mAllowContentXBLScope; +}; + +/***************************************************************************/ +// Slots we use for our functions +#define XPC_FUNCTION_NATIVE_MEMBER_SLOT 0 +#define XPC_FUNCTION_PARENT_OBJECT_SLOT 1 + +/***************************************************************************/ +// XPCNativeMember represents a single idl declared method, attribute or +// constant. + +// Tight. No virtual methods. Can be bitwise copied (until any resolution done). + +class XPCNativeMember final { + public: + static bool GetCallInfo(JSObject* funobj, + RefPtr<XPCNativeInterface>* pInterface, + XPCNativeMember** pMember); + + jsid GetName() const { return mName; } + + uint16_t GetIndex() const { return mIndex; } + + bool GetConstantValue(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::Value* pval) { + MOZ_ASSERT(IsConstant(), + "Only call this if you're sure this is a constant!"); + return Resolve(ccx, iface, nullptr, pval); + } + + bool NewFunctionObject(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::HandleObject parent, JS::Value* pval); + + bool IsMethod() const { return 0 != (mFlags & METHOD); } + + bool IsConstant() const { return 0 != (mFlags & CONSTANT); } + + bool IsAttribute() const { return 0 != (mFlags & GETTER); } + + bool IsWritableAttribute() const { return 0 != (mFlags & SETTER_TOO); } + + bool IsReadOnlyAttribute() const { + return IsAttribute() && !IsWritableAttribute(); + } + + void SetName(jsid a) { mName = a; } + + void SetMethod(uint16_t index) { + mFlags = METHOD; + mIndex = index; + } + + void SetConstant(uint16_t index) { + mFlags = CONSTANT; + mIndex = index; + } + + void SetReadOnlyAttribute(uint16_t index) { + mFlags = GETTER; + mIndex = index; + } + + void SetWritableAttribute() { + MOZ_ASSERT(mFlags == GETTER, "bad"); + mFlags = GETTER | SETTER_TOO; + } + + static uint16_t GetMaxIndexInInterface() { return (1 << 12) - 1; } + + inline XPCNativeInterface* GetInterface() const; + + void SetIndexInInterface(uint16_t index) { mIndexInInterface = index; } + + /* default ctor - leave random contents */ + MOZ_COUNTED_DEFAULT_CTOR(XPCNativeMember) + MOZ_COUNTED_DTOR(XPCNativeMember) + + XPCNativeMember(const XPCNativeMember& other) + : mName(other.mName), + mIndex(other.mIndex), + mFlags(other.mFlags), + mIndexInInterface(other.mIndexInInterface) { + MOZ_COUNT_CTOR(XPCNativeMember); + } + + private: + bool Resolve(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::HandleObject parent, JS::Value* vp); + + enum { + METHOD = 0x01, + CONSTANT = 0x02, + GETTER = 0x04, + SETTER_TOO = 0x08 + // If you add a flag here, you may need to make mFlags wider and either + // make mIndexInInterface narrower (and adjust + // XPCNativeInterface::NewInstance accordingly) or make this object + // bigger. + }; + + private: + // our only data... + jsid mName; + uint16_t mIndex; + // mFlags needs to be wide enough to hold the flags in the above enum. + uint16_t mFlags : 4; + // mIndexInInterface is the index of this in our XPCNativeInterface's + // mMembers. In theory our XPCNativeInterface could have as many as 2^15-1 + // members (since mMemberCount is 15-bit) but in practice we prevent + // creation of XPCNativeInterfaces which have more than 2^12 members. + // If the width of this field changes, update GetMaxIndexInInterface. + uint16_t mIndexInInterface : 12; +}; + +/***************************************************************************/ +// XPCNativeInterface represents a single idl declared interface. This is +// primarily the set of XPCNativeMembers. + +// Tight. No virtual methods. + +class XPCNativeInterface final { + public: + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeInterface, + DestroyInstance(this)) + + static already_AddRefed<XPCNativeInterface> GetNewOrUsed(JSContext* cx, + const nsIID* iid); + static already_AddRefed<XPCNativeInterface> GetNewOrUsed( + JSContext* cx, const nsXPTInterfaceInfo* info); + static already_AddRefed<XPCNativeInterface> GetNewOrUsed(JSContext* cx, + const char* name); + static already_AddRefed<XPCNativeInterface> GetISupports(JSContext* cx); + + inline const nsXPTInterfaceInfo* GetInterfaceInfo() const { return mInfo; } + inline jsid GetName() const { return mName; } + + inline const nsIID* GetIID() const; + inline const char* GetNameString() const; + inline XPCNativeMember* FindMember(jsid name) const; + + static inline size_t OffsetOfMembers(); + + uint16_t GetMemberCount() const { return mMemberCount; } + XPCNativeMember* GetMemberAt(uint16_t i) { + MOZ_ASSERT(i < mMemberCount, "bad index"); + return &mMembers[i]; + } + + void DebugDump(int16_t depth); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + void Trace(JSTracer* trc); + + protected: + static already_AddRefed<XPCNativeInterface> NewInstance( + JSContext* cx, IID2NativeInterfaceMap* aMap, + const nsXPTInterfaceInfo* aInfo); + + XPCNativeInterface() = delete; + XPCNativeInterface(const nsXPTInterfaceInfo* aInfo, jsid aName) + : mInfo(aInfo), mName(aName), mMemberCount(0) {} + ~XPCNativeInterface(); + + void* operator new(size_t, void* p) noexcept(true) { return p; } + + XPCNativeInterface(const XPCNativeInterface& r) = delete; + XPCNativeInterface& operator=(const XPCNativeInterface& r) = delete; + + static void DestroyInstance(XPCNativeInterface* inst); + + private: + const nsXPTInterfaceInfo* mInfo; + jsid mName; + uint16_t mMemberCount; + XPCNativeMember mMembers[1]; // always last - object sized for array +}; + +/***************************************************************************/ +// XPCNativeSetKey is used to key a XPCNativeSet in a NativeSetMap. +// It represents a new XPCNativeSet we are considering constructing, without +// requiring that the set actually be built. + +class MOZ_STACK_CLASS XPCNativeSetKey final { + public: + // This represents an existing set |baseSet|. + explicit XPCNativeSetKey(XPCNativeSet* baseSet) + : mCx(nullptr), mBaseSet(baseSet), mAddition(nullptr) { + MOZ_ASSERT(baseSet); + } + + // This represents a new set containing only nsISupports and + // |addition|. This needs a JSContext because it may need to + // construct some data structures that need one to construct them. + explicit XPCNativeSetKey(JSContext* cx, XPCNativeInterface* addition) + : mCx(cx), mBaseSet(nullptr), mAddition(addition) { + MOZ_ASSERT(cx); + MOZ_ASSERT(addition); + } + + // This represents the existing set |baseSet| with the interface + // |addition| inserted after existing interfaces. |addition| must + // not already be present in |baseSet|. + explicit XPCNativeSetKey(XPCNativeSet* baseSet, XPCNativeInterface* addition); + ~XPCNativeSetKey() = default; + + XPCNativeSet* GetBaseSet() const { return mBaseSet; } + XPCNativeInterface* GetAddition() const { return mAddition; } + + mozilla::HashNumber Hash() const; + + // Allow shallow copy + + private: + JSContext* mCx; + RefPtr<XPCNativeSet> mBaseSet; + RefPtr<XPCNativeInterface> mAddition; +}; + +/***************************************************************************/ +// XPCNativeSet represents an ordered collection of XPCNativeInterface pointers. + +class XPCNativeSet final { + public: + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeSet, DestroyInstance(this)) + + static already_AddRefed<XPCNativeSet> GetNewOrUsed(JSContext* cx, + const nsIID* iid); + static already_AddRefed<XPCNativeSet> GetNewOrUsed(JSContext* cx, + nsIClassInfo* classInfo); + static already_AddRefed<XPCNativeSet> GetNewOrUsed(JSContext* cx, + XPCNativeSetKey* key); + + // This generates a union set. + // + // If preserveFirstSetOrder is true, the elements from |firstSet| come first, + // followed by any non-duplicate items from |secondSet|. If false, the same + // algorithm is applied; but if we detect that |secondSet| is a superset of + // |firstSet|, we return |secondSet| without worrying about whether the + // ordering might differ from |firstSet|. + static already_AddRefed<XPCNativeSet> GetNewOrUsed( + JSContext* cx, XPCNativeSet* firstSet, XPCNativeSet* secondSet, + bool preserveFirstSetOrder); + + static void ClearCacheEntryForClassInfo(nsIClassInfo* classInfo); + + inline bool FindMember(jsid name, XPCNativeMember** pMember, + uint16_t* pInterfaceIndex) const; + + inline bool FindMember(jsid name, XPCNativeMember** pMember, + RefPtr<XPCNativeInterface>* pInterface) const; + + inline bool FindMember(JS::HandleId name, XPCNativeMember** pMember, + RefPtr<XPCNativeInterface>* pInterface, + XPCNativeSet* protoSet, bool* pIsLocal) const; + + inline bool HasInterface(XPCNativeInterface* aInterface) const; + + uint16_t GetInterfaceCount() const { return mInterfaceCount; } + XPCNativeInterface** GetInterfaceArray() { return mInterfaces; } + + XPCNativeInterface* GetInterfaceAt(uint16_t i) { + MOZ_ASSERT(i < mInterfaceCount, "bad index"); + return mInterfaces[i]; + } + + inline bool MatchesSetUpToInterface(const XPCNativeSet* other, + XPCNativeInterface* iface) const; + + void DebugDump(int16_t depth); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + protected: + static already_AddRefed<XPCNativeSet> NewInstance( + JSContext* cx, nsTArray<RefPtr<XPCNativeInterface>>&& array); + static already_AddRefed<XPCNativeSet> NewInstanceMutate(XPCNativeSetKey* key); + + XPCNativeSet() : mInterfaceCount(0) {} + ~XPCNativeSet(); + void* operator new(size_t, void* p) noexcept(true) { return p; } + + static void DestroyInstance(XPCNativeSet* inst); + + private: + uint16_t mInterfaceCount; + // Always last - object sized for array. + // These are strong references. + XPCNativeInterface* mInterfaces[1]; +}; + +/***********************************************/ +// XPCWrappedNativeProtos hold the additional shared wrapper data for +// XPCWrappedNative whose native objects expose nsIClassInfo. +// +// The XPCWrappedNativeProto is owned by its mJSProtoObject, until that object +// is finalized. After that, it is owned by XPCJSRuntime's +// mDyingWrappedNativeProtos. See XPCWrappedNativeProto::JSProtoObjectFinalized +// and XPCJSRuntime::FinalizeCallback. + +class XPCWrappedNativeProto final { + public: + enum Slots { ProtoSlot, SlotCount }; + + static XPCWrappedNativeProto* GetNewOrUsed(JSContext* cx, + XPCWrappedNativeScope* scope, + nsIClassInfo* classInfo, + nsIXPCScriptable* scriptable); + + XPCWrappedNativeScope* GetScope() const { return mScope; } + + XPCJSRuntime* GetRuntime() const { return mScope->GetRuntime(); } + + JSObject* GetJSProtoObject() const { return mJSProtoObject; } + + JSObject* GetJSProtoObjectPreserveColor() const { + return mJSProtoObject.unbarrieredGet(); + } + + nsIClassInfo* GetClassInfo() const { return mClassInfo; } + + XPCNativeSet* GetSet() const { return mSet; } + + nsIXPCScriptable* GetScriptable() const { return mScriptable; } + + void JSProtoObjectFinalized(JS::GCContext* gcx, JSObject* obj); + void JSProtoObjectMoved(JSObject* obj, const JSObject* old); + + static XPCWrappedNativeProto* Get(JSObject* obj); + + void SystemIsBeingShutDown(); + + void DebugDump(int16_t depth); + + void TraceSelf(JSTracer* trc) { + if (mJSProtoObject) { + TraceEdge(trc, &mJSProtoObject, "XPCWrappedNativeProto::mJSProtoObject"); + } + } + + void TraceJS(JSTracer* trc) { TraceSelf(trc); } + + // NOP. This is just here to make the AutoMarkingPtr code compile. + void Mark() const {} + inline void AutoTrace(JSTracer* trc) {} + + ~XPCWrappedNativeProto(); + + protected: + // disable copy ctor and assignment + XPCWrappedNativeProto(const XPCWrappedNativeProto& r) = delete; + XPCWrappedNativeProto& operator=(const XPCWrappedNativeProto& r) = delete; + + // hide ctor + XPCWrappedNativeProto(XPCWrappedNativeScope* Scope, nsIClassInfo* ClassInfo, + RefPtr<XPCNativeSet>&& Set); + + bool Init(JSContext* cx, nsIXPCScriptable* scriptable); + + private: +#ifdef DEBUG + static int32_t gDEBUG_LiveProtoCount; +#endif + + private: + XPCWrappedNativeScope* mScope; + JS::Heap<JSObject*> mJSProtoObject; + nsCOMPtr<nsIClassInfo> mClassInfo; + RefPtr<XPCNativeSet> mSet; + nsCOMPtr<nsIXPCScriptable> mScriptable; +}; + +/***********************************************/ +// XPCWrappedNativeTearOff represents the info needed to make calls to one +// interface on the underlying native object of a XPCWrappedNative. + +class XPCWrappedNativeTearOff final { + public: + enum Slots { FlatObjectSlot, TearOffSlot, SlotCount }; + + bool IsAvailable() const { return mInterface == nullptr; } + bool IsReserved() const { return mInterface == (XPCNativeInterface*)1; } + bool IsValid() const { return !IsAvailable() && !IsReserved(); } + void SetReserved() { mInterface = (XPCNativeInterface*)1; } + + XPCNativeInterface* GetInterface() const { return mInterface; } + nsISupports* GetNative() const { return mNative; } + JSObject* GetJSObject(); + JSObject* GetJSObjectPreserveColor() const; + void SetInterface(XPCNativeInterface* Interface) { mInterface = Interface; } + void SetNative(nsISupports* Native) { mNative = Native; } + already_AddRefed<nsISupports> TakeNative() { return mNative.forget(); } + void SetJSObject(JSObject* JSObj); + + void JSObjectFinalized() { SetJSObject(nullptr); } + void JSObjectMoved(JSObject* obj, const JSObject* old); + + static XPCWrappedNativeTearOff* Get(JSObject* obj); + + XPCWrappedNativeTearOff() : mInterface(nullptr), mJSObject(nullptr) { + MOZ_COUNT_CTOR(XPCWrappedNativeTearOff); + } + ~XPCWrappedNativeTearOff(); + + // NOP. This is just here to make the AutoMarkingPtr code compile. + inline void TraceJS(JSTracer* trc) {} + inline void AutoTrace(JSTracer* trc) {} + + void Mark() { mJSObject.setFlags(1); } + void Unmark() { mJSObject.unsetFlags(1); } + bool IsMarked() const { return mJSObject.hasFlag(1); } + + XPCWrappedNativeTearOff* AddTearOff() { + MOZ_ASSERT(!mNextTearOff); + mNextTearOff = mozilla::MakeUnique<XPCWrappedNativeTearOff>(); + return mNextTearOff.get(); + } + + XPCWrappedNativeTearOff* GetNextTearOff() { return mNextTearOff.get(); } + + private: + XPCWrappedNativeTearOff(const XPCWrappedNativeTearOff& r) = delete; + XPCWrappedNativeTearOff& operator=(const XPCWrappedNativeTearOff& r) = delete; + + private: + XPCNativeInterface* mInterface; + // mNative is an nsRefPtr not an nsCOMPtr because it may not be the canonical + // nsISupports pointer. + RefPtr<nsISupports> mNative; + JS::TenuredHeap<JSObject*> mJSObject; + mozilla::UniquePtr<XPCWrappedNativeTearOff> mNextTearOff; +}; + +/***************************************************************************/ +// XPCWrappedNative the wrapper around one instance of a native xpcom object +// to be used from JavaScript. + +class XPCWrappedNative final : public nsIXPConnectWrappedNative { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + + NS_DECL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) + + JSObject* GetJSObject() override; + + bool IsValid() const { return mFlatJSObject.hasFlag(FLAT_JS_OBJECT_VALID); } + + nsresult DebugDump(int16_t depth); + +#define XPC_SCOPE_WORD(s) (intptr_t(s)) +#define XPC_SCOPE_MASK (intptr_t(0x3)) +#define XPC_SCOPE_TAG (intptr_t(0x1)) +#define XPC_WRAPPER_EXPIRED (intptr_t(0x2)) + + static inline bool IsTaggedScope(XPCWrappedNativeScope* s) { + return XPC_SCOPE_WORD(s) & XPC_SCOPE_TAG; + } + + static inline XPCWrappedNativeScope* TagScope(XPCWrappedNativeScope* s) { + MOZ_ASSERT(!IsTaggedScope(s), "bad pointer!"); + return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) | XPC_SCOPE_TAG); + } + + static inline XPCWrappedNativeScope* UnTagScope(XPCWrappedNativeScope* s) { + return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) & ~XPC_SCOPE_TAG); + } + + inline bool IsWrapperExpired() const { + return XPC_SCOPE_WORD(mMaybeScope) & XPC_WRAPPER_EXPIRED; + } + + bool HasProto() const { return !IsTaggedScope(mMaybeScope); } + + XPCWrappedNativeProto* GetProto() const { + return HasProto() ? (XPCWrappedNativeProto*)(XPC_SCOPE_WORD(mMaybeProto) & + ~XPC_SCOPE_MASK) + : nullptr; + } + + XPCWrappedNativeScope* GetScope() const { + return GetProto() ? GetProto()->GetScope() + : (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(mMaybeScope) & + ~XPC_SCOPE_MASK); + } + + nsISupports* GetIdentityObject() const { return mIdentity; } + + /** + * This getter clears the gray bit before handing out the JSObject which + * means that the object is guaranteed to be kept alive past the next CC. + */ + JSObject* GetFlatJSObject() const { return mFlatJSObject; } + + /** + * This getter does not change the color of the JSObject meaning that the + * object returned is not guaranteed to be kept alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JSObject* GetFlatJSObjectPreserveColor() const { + return mFlatJSObject.unbarrieredGetPtr(); + } + + XPCNativeSet* GetSet() const { return mSet; } + + void SetSet(already_AddRefed<XPCNativeSet> set) { mSet = set; } + + static XPCWrappedNative* Get(JSObject* obj) { + MOZ_ASSERT(xpc::IsWrappedNativeReflector(obj)); + return JS::GetObjectISupports<XPCWrappedNative>(obj); + } + + private: + void SetFlatJSObject(JSObject* object); + void UnsetFlatJSObject(); + + inline void ExpireWrapper() { + mMaybeScope = (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(mMaybeScope) | + XPC_WRAPPER_EXPIRED); + } + + public: + nsIXPCScriptable* GetScriptable() const { return mScriptable; } + + nsIClassInfo* GetClassInfo() const { + return IsValid() && HasProto() ? GetProto()->GetClassInfo() : nullptr; + } + + bool HasMutatedSet() const { + return IsValid() && (!HasProto() || GetSet() != GetProto()->GetSet()); + } + + XPCJSRuntime* GetRuntime() const { + XPCWrappedNativeScope* scope = GetScope(); + return scope ? scope->GetRuntime() : nullptr; + } + + static nsresult WrapNewGlobal(JSContext* cx, xpcObjectHelper& nativeHelper, + nsIPrincipal* principal, + bool initStandardClasses, + JS::RealmOptions& aOptions, + XPCWrappedNative** wrappedGlobal); + + static nsresult GetNewOrUsed(JSContext* cx, xpcObjectHelper& helper, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** wrapper); + + void FlatJSObjectFinalized(); + void FlatJSObjectMoved(JSObject* obj, const JSObject* old); + + void SystemIsBeingShutDown(); + + enum CallMode { CALL_METHOD, CALL_GETTER, CALL_SETTER }; + + static bool CallMethod(XPCCallContext& ccx, CallMode mode = CALL_METHOD); + + static bool GetAttribute(XPCCallContext& ccx) { + return CallMethod(ccx, CALL_GETTER); + } + + static bool SetAttribute(XPCCallContext& ccx) { + return CallMethod(ccx, CALL_SETTER); + } + + XPCWrappedNativeTearOff* FindTearOff(JSContext* cx, + XPCNativeInterface* aInterface, + bool needJSObject = false, + nsresult* pError = nullptr); + XPCWrappedNativeTearOff* FindTearOff(JSContext* cx, const nsIID& iid); + + void Mark() const {} + + inline void TraceInside(JSTracer* trc) { + if (HasProto()) { + GetProto()->TraceSelf(trc); + } + + JSObject* obj = mFlatJSObject.unbarrieredGetPtr(); + if (obj && JS_IsGlobalObject(obj)) { + xpc::TraceXPCGlobal(trc, obj); + } + } + + void TraceJS(JSTracer* trc) { TraceInside(trc); } + + void TraceSelf(JSTracer* trc) { + // If this got called, we're being kept alive by someone who really + // needs us alive and whole. Do not let our mFlatJSObject go away. + // This is the only time we should be tracing our mFlatJSObject, + // normally somebody else is doing that. + JS::TraceEdge(trc, &mFlatJSObject, "XPCWrappedNative::mFlatJSObject"); + } + + static void Trace(JSTracer* trc, JSObject* obj); + + void AutoTrace(JSTracer* trc) { TraceSelf(trc); } + + inline void SweepTearOffs(); + + // Returns a string that should be freed with js_free, or nullptr on + // failure. + char* ToString(XPCWrappedNativeTearOff* to = nullptr) const; + + static nsIXPCScriptable* GatherProtoScriptable(nsIClassInfo* classInfo); + + bool HasExternalReference() const { return mRefCnt > 1; } + + void Suspect(nsCycleCollectionNoteRootCallback& cb); + void NoteTearoffs(nsCycleCollectionTraversalCallback& cb); + + // Make ctor and dtor protected (rather than private) to placate nsCOMPtr. + protected: + XPCWrappedNative() = delete; + + // This ctor is used if this object will have a proto. + XPCWrappedNative(nsCOMPtr<nsISupports>&& aIdentity, + XPCWrappedNativeProto* aProto); + + // This ctor is used if this object will NOT have a proto. + XPCWrappedNative(nsCOMPtr<nsISupports>&& aIdentity, + XPCWrappedNativeScope* aScope, RefPtr<XPCNativeSet>&& aSet); + + virtual ~XPCWrappedNative(); + void Destroy(); + + private: + enum { + // Flags bits for mFlatJSObject: + FLAT_JS_OBJECT_VALID = js::Bit(0) + }; + + bool Init(JSContext* cx, nsIXPCScriptable* scriptable); + bool FinishInit(JSContext* cx); + + bool ExtendSet(JSContext* aCx, XPCNativeInterface* aInterface); + + nsresult InitTearOff(JSContext* cx, XPCWrappedNativeTearOff* aTearOff, + XPCNativeInterface* aInterface, bool needJSObject); + + bool InitTearOffJSObject(JSContext* cx, XPCWrappedNativeTearOff* to); + + public: + static void GatherScriptable(nsISupports* obj, nsIClassInfo* classInfo, + nsIXPCScriptable** scrProto, + nsIXPCScriptable** scrWrapper); + + private: + union { + XPCWrappedNativeScope* mMaybeScope; + XPCWrappedNativeProto* mMaybeProto; + }; + RefPtr<XPCNativeSet> mSet; + JS::TenuredHeap<JSObject*> mFlatJSObject; + nsCOMPtr<nsIXPCScriptable> mScriptable; + XPCWrappedNativeTearOff mFirstTearOff; +}; + +/*************************************************************************** +**************************************************************************** +* +* Core classes for wrapped JSObject for use from native code... +* +**************************************************************************** +***************************************************************************/ + +/*************************/ +// nsXPCWrappedJS is a wrapper for a single JSObject for use from native code. +// nsXPCWrappedJS objects are chained together to represent the various +// interface on the single underlying (possibly aggregate) JSObject. + +class nsXPCWrappedJS final : protected nsAutoXPTCStub, + public nsIXPConnectWrappedJSUnmarkGray, + public nsSupportsWeakReference, + public mozilla::LinkedListElement<nsXPCWrappedJS> { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSISUPPORTSWEAKREFERENCE + + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS( + nsXPCWrappedJS, nsIXPConnectWrappedJS) + + JSObject* GetJSObject() override; + + // This method is defined in XPCWrappedJSClass.cpp to preserve VCS blame. + NS_IMETHOD CallMethod(uint16_t methodIndex, const nsXPTMethodInfo* info, + nsXPTCMiniVariant* nativeParams) override; + + /* + * This is rarely called directly. Instead one usually calls + * XPCConvert::JSObject2NativeInterface which will handles cases where the + * JS object is already a wrapped native or a DOM object. + */ + + static nsresult GetNewOrUsed(JSContext* cx, JS::HandleObject aJSObj, + REFNSIID aIID, nsXPCWrappedJS** wrapper); + + nsISomeInterface* GetXPTCStub() { return mXPTCStub; } + + nsresult DebugDump(int16_t depth); + + /** + * This getter does not change the color of the JSObject meaning that the + * object returned is not guaranteed to be kept alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JSObject* GetJSObjectPreserveColor() const { return mJSObj.unbarrieredGet(); } + + // Returns true if the wrapper chain contains references to multiple + // compartments. If the wrapper chain contains references to multiple + // compartments, then it must be registered on the XPCJSContext. Otherwise, + // it should be registered in the CompartmentPrivate for the compartment of + // the root's JS object. This will only return correct results when called + // on the root wrapper and will assert if not called on a root wrapper. + bool IsMultiCompartment() const; + + const nsXPTInterfaceInfo* GetInfo() const { return mInfo; } + REFNSIID GetIID() const { return mInfo->IID(); } + nsXPCWrappedJS* GetRootWrapper() const { return mRoot; } + nsXPCWrappedJS* GetNextWrapper() const { return mNext; } + + nsXPCWrappedJS* Find(REFNSIID aIID); + nsXPCWrappedJS* FindInherited(REFNSIID aIID); + nsXPCWrappedJS* FindOrFindInherited(REFNSIID aIID) { + nsXPCWrappedJS* wrapper = Find(aIID); + if (wrapper) { + return wrapper; + } + return FindInherited(aIID); + } + + bool IsRootWrapper() const { return mRoot == this; } + bool IsValid() const { return bool(mJSObj); } + void SystemIsBeingShutDown(); + + // These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects + // to find non-rooting wrappers for dying JS objects. See the top of + // XPCWrappedJS.cpp for more details. + bool IsSubjectToFinalization() const { return IsValid() && mRefCnt == 1; } + + void UpdateObjectPointerAfterGC(JSTracer* trc) { + MOZ_ASSERT(IsRootWrapper()); + JS_UpdateWeakPointerAfterGC(trc, &mJSObj); + } + + bool IsAggregatedToNative() const { return mRoot->mOuter != nullptr; } + nsISupports* GetAggregatedNativeObject() const { return mRoot->mOuter; } + void SetAggregatedNativeObject(nsISupports* aNative) { + MOZ_ASSERT(aNative); + if (mRoot->mOuter) { + MOZ_ASSERT(mRoot->mOuter == aNative, + "Only one aggregated native can be set"); + return; + } + mRoot->mOuter = aNative; + } + + // This method is defined in XPCWrappedJSClass.cpp to preserve VCS blame. + static void DebugDumpInterfaceInfo(const nsXPTInterfaceInfo* aInfo, + int16_t depth); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + virtual ~nsXPCWrappedJS(); + + protected: + nsXPCWrappedJS() = delete; + nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, + const nsXPTInterfaceInfo* aInfo, nsXPCWrappedJS* root, + nsresult* rv); + + bool CanSkip(); + void Destroy(); + void Unlink(); + + private: + friend class nsIXPConnectWrappedJS; + + JS::Compartment* Compartment() const { + return JS::GetCompartment(mJSObj.unbarrieredGet()); + } + + // These methods are defined in XPCWrappedJSClass.cpp to preserve VCS blame. + static const nsXPTInterfaceInfo* GetInterfaceInfo(REFNSIID aIID); + + nsresult DelegatedQueryInterface(REFNSIID aIID, void** aInstancePtr); + + static JSObject* GetRootJSObject(JSContext* cx, JSObject* aJSObj); + + static JSObject* CallQueryInterfaceOnJSObject(JSContext* cx, JSObject* jsobj, + JS::HandleObject scope, + REFNSIID aIID); + + // aObj is the nsXPCWrappedJS's object. We used this as the callee (or |this| + // if getter or setter). + // aSyntheticException, if not null, is the exception we should be using. + // If null, look for an exception on the JSContext hanging off the + // XPCCallContext. + static nsresult CheckForException( + XPCCallContext& ccx, mozilla::dom::AutoEntryScript& aes, + JS::HandleObject aObj, const char* aPropertyName, + const char* anInterfaceName, + mozilla::dom::Exception* aSyntheticException = nullptr); + + static bool GetArraySizeFromParam(const nsXPTMethodInfo* method, + const nsXPTType& type, + nsXPTCMiniVariant* params, + uint32_t* result); + + static bool GetInterfaceTypeFromParam(const nsXPTMethodInfo* method, + const nsXPTType& type, + nsXPTCMiniVariant* params, + nsID* result); + + static void CleanupOutparams(const nsXPTMethodInfo* info, + nsXPTCMiniVariant* nativeParams, bool inOutOnly, + uint8_t count); + + JS::Heap<JSObject*> mJSObj; + const nsXPTInterfaceInfo* const mInfo; + nsXPCWrappedJS* mRoot; // If mRoot != this, it is an owning pointer. + nsXPCWrappedJS* mNext; + nsCOMPtr<nsISupports> mOuter; // only set in root +}; + +/*************************************************************************** +**************************************************************************** +* +* All manner of utility classes follow... +* +**************************************************************************** +***************************************************************************/ + +namespace xpc { + +// A wrapper around JS iterators which presents an equivalent +// nsISimpleEnumerator interface for their contents. +class XPCWrappedJSIterator final : public nsISimpleEnumerator { + public: + NS_DECL_CYCLE_COLLECTION_CLASS(XPCWrappedJSIterator) + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSISIMPLEENUMERATORBASE + + explicit XPCWrappedJSIterator(nsIJSEnumerator* aEnum); + + private: + ~XPCWrappedJSIterator() = default; + + nsCOMPtr<nsIJSEnumerator> mEnum; + nsCOMPtr<nsIGlobalObject> mGlobal; + nsCOMPtr<nsISupports> mNext; + mozilla::Maybe<bool> mHasNext; +}; + +} // namespace xpc + +/***************************************************************************/ +// class here just for static methods +class XPCConvert { + public: + /** + * Convert a native object into a JS::Value. + * + * @param cx the JSContext representing the global we want the value in + * @param d [out] the resulting JS::Value + * @param s the native object we're working with + * @param type the type of object that s is + * @param iid the interface of s that we want + * @param scope the default scope to put on the new JSObject's parent + * chain + * @param pErr [out] relevant error code, if any. + */ + + static bool NativeData2JS(JSContext* cx, JS::MutableHandleValue d, + const void* s, const nsXPTType& type, + const nsID* iid, uint32_t arrlen, nsresult* pErr); + + static bool JSData2Native(JSContext* cx, void* d, JS::HandleValue s, + const nsXPTType& type, const nsID* iid, + uint32_t arrlen, nsresult* pErr); + + /** + * Convert a native nsISupports into a JSObject. + * + * @param cx the JSContext representing the global we want the object in. + * @param dest [out] the resulting JSObject + * @param src the native object we're working with + * @param iid the interface of src that we want (may be null) + * @param cache the wrapper cache for src (may be null, in which case src + * will be QI'ed to get the cache) + * @param allowNativeWrapper if true, this method may wrap the resulting + * JSObject in an XPCNativeWrapper and return that, as needed. + * @param pErr [out] relevant error code, if any. + * @param src_is_identity optional performance hint. Set to true only + * if src is the identity pointer. + */ + static bool NativeInterface2JSObject(JSContext* cx, + JS::MutableHandleValue dest, + xpcObjectHelper& aHelper, + const nsID* iid, bool allowNativeWrapper, + nsresult* pErr); + + static bool GetNativeInterfaceFromJSObject(void** dest, JSObject* src, + const nsID* iid, nsresult* pErr); + static bool JSObject2NativeInterface(JSContext* cx, void** dest, + JS::HandleObject src, const nsID* iid, + nsISupports* aOuter, nsresult* pErr); + + // Note - This return the XPCWrappedNative, rather than the native itself, + // for the WN case. You probably want UnwrapReflectorToISupports. + static bool GetISupportsFromJSObject(JSObject* obj, nsISupports** iface); + + static nsresult JSValToXPCException(JSContext* cx, JS::MutableHandleValue s, + const char* ifaceName, + const char* methodName, + mozilla::dom::Exception** exception); + + static nsresult ConstructException(nsresult rv, const char* message, + const char* ifaceName, + const char* methodName, nsISupports* data, + mozilla::dom::Exception** exception, + JSContext* cx, JS::Value* jsExceptionPtr); + + private: + /** + * Convert a native array into a JS::Value. + * + * @param cx the JSContext we're working with and in whose global the array + * should be created. + * @param d [out] the resulting JS::Value + * @param buf the native buffer containing input values + * @param type the type of objects in the array + * @param iid the interface of each object in the array that we want + * @param count the number of items in the array + * @param scope the default scope to put on the new JSObjects' parent chain + * @param pErr [out] relevant error code, if any. + */ + static bool NativeArray2JS(JSContext* cx, JS::MutableHandleValue d, + const void* buf, const nsXPTType& type, + const nsID* iid, uint32_t count, nsresult* pErr); + + using ArrayAllocFixupLen = std::function<void*(uint32_t*)>; + + /** + * Convert a JS::Value into a native array. + * + * @param cx the JSContext we're working with + * @param aJSVal the JS::Value to convert + * @param aEltType the type of objects in the array + * @param aIID the interface of each object in the array + * @param pErr [out] relevant error code, if any + * @param aAllocFixupLen function called with the JS Array's length to + * allocate the backing buffer. This function may + * modify the length of array to be converted. + */ + static bool JSArray2Native(JSContext* cx, JS::HandleValue aJSVal, + const nsXPTType& aEltType, const nsIID* aIID, + nsresult* pErr, + const ArrayAllocFixupLen& aAllocFixupLen); + + XPCConvert() = delete; +}; + +/***************************************************************************/ +// code for throwing exceptions into JS + +class nsXPCException; + +class XPCThrower { + public: + static void Throw(nsresult rv, JSContext* cx); + static void Throw(nsresult rv, XPCCallContext& ccx); + static void ThrowBadResult(nsresult rv, nsresult result, XPCCallContext& ccx); + static void ThrowBadParam(nsresult rv, unsigned paramNum, + XPCCallContext& ccx); + static bool SetVerbosity(bool state) { + bool old = sVerbose; + sVerbose = state; + return old; + } + + static bool CheckForPendingException(nsresult result, JSContext* cx); + + private: + static void Verbosify(XPCCallContext& ccx, char** psz, bool own); + + private: + static bool sVerbose; +}; + +/***************************************************************************/ + +class nsXPCException { + public: + static bool NameAndFormatForNSResult(nsresult rv, const char** name, + const char** format); + + static const void* IterateNSResults(nsresult* rv, const char** name, + const char** format, const void** iterp); + + static uint32_t GetNSResultCount(); +}; + +/***************************************************************************/ +// 'Components' object implementation. + +class nsXPCComponents final : public nsIXPCComponents { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS + + public: + void SystemIsBeingShutDown() { ClearMembers(); } + + XPCWrappedNativeScope* GetScope() { return mScope; } + + protected: + ~nsXPCComponents(); + + explicit nsXPCComponents(XPCWrappedNativeScope* aScope); + void ClearMembers(); + + XPCWrappedNativeScope* mScope; + + RefPtr<nsXPCComponents_Interfaces> mInterfaces; + RefPtr<nsXPCComponents_Results> mResults; + RefPtr<nsXPCComponents_Classes> mClasses; + RefPtr<nsXPCComponents_ID> mID; + RefPtr<nsXPCComponents_Exception> mException; + RefPtr<nsXPCComponents_Constructor> mConstructor; + RefPtr<nsXPCComponents_Utils> mUtils; + + friend class XPCWrappedNativeScope; +}; + +/****************************************************************************** + * Handles pre/post script processing. + */ +class MOZ_RAII AutoScriptEvaluate { + public: + /** + * Saves the JSContext as well as initializing our state + * @param cx The JSContext, this can be null, we don't do anything then + */ + explicit AutoScriptEvaluate(JSContext* cx) + : mJSContext(cx), mEvaluated(false) {} + + /** + * Does the pre script evaluation. + * This function should only be called once, and will assert if called + * more than once + */ + + bool StartEvaluating(JS::HandleObject scope); + + /** + * Does the post script evaluation. + */ + ~AutoScriptEvaluate(); + + private: + JSContext* mJSContext; + mozilla::Maybe<JS::AutoSaveExceptionState> mState; + bool mEvaluated; + mozilla::Maybe<JSAutoRealm> mAutoRealm; + + // No copying or assignment allowed + AutoScriptEvaluate(const AutoScriptEvaluate&) = delete; + AutoScriptEvaluate& operator=(const AutoScriptEvaluate&) = delete; +}; + +/***************************************************************************/ +class MOZ_RAII AutoResolveName { + public: + AutoResolveName(XPCCallContext& ccx, JS::HandleId name) + : mContext(ccx.GetContext()), + mOld(ccx, mContext->SetResolveName(name)) +#ifdef DEBUG + , + mCheck(ccx, name) +#endif + { + } + + ~AutoResolveName() { + mozilla::DebugOnly<jsid> old = mContext->SetResolveName(mOld); + MOZ_ASSERT(old == mCheck, "Bad Nesting!"); + } + + private: + XPCJSContext* mContext; + JS::RootedId mOld; +#ifdef DEBUG + JS::RootedId mCheck; +#endif +}; + +/***************************************************************************/ +// AutoMarkingPtr is the base class for the various AutoMarking pointer types +// below. This system allows us to temporarily protect instances of our garbage +// collected types after they are constructed but before they are safely +// attached to other rooted objects. +// This base class has pure virtual support for marking. + +class AutoMarkingPtr { + public: + explicit AutoMarkingPtr(JSContext* cx) { + mRoot = XPCJSContext::Get()->GetAutoRootsAdr(); + mNext = *mRoot; + *mRoot = this; + } + + virtual ~AutoMarkingPtr() { + if (mRoot) { + MOZ_ASSERT(*mRoot == this); + *mRoot = mNext; + } + } + + void TraceJSAll(JSTracer* trc) { + for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) { + cur->TraceJS(trc); + } + } + + void MarkAfterJSFinalizeAll() { + for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) { + cur->MarkAfterJSFinalize(); + } + } + + protected: + virtual void TraceJS(JSTracer* trc) = 0; + virtual void MarkAfterJSFinalize() = 0; + + private: + AutoMarkingPtr** mRoot; + AutoMarkingPtr* mNext; +}; + +template <class T> +class TypedAutoMarkingPtr : public AutoMarkingPtr { + public: + explicit TypedAutoMarkingPtr(JSContext* cx) + : AutoMarkingPtr(cx), mPtr(nullptr) {} + TypedAutoMarkingPtr(JSContext* cx, T* ptr) : AutoMarkingPtr(cx), mPtr(ptr) {} + + T* get() const { return mPtr; } + operator T*() const { return mPtr; } + T* operator->() const { return mPtr; } + + TypedAutoMarkingPtr<T>& operator=(T* ptr) { + mPtr = ptr; + return *this; + } + + protected: + virtual void TraceJS(JSTracer* trc) override { + if (mPtr) { + mPtr->TraceJS(trc); + mPtr->AutoTrace(trc); + } + } + + virtual void MarkAfterJSFinalize() override { + if (mPtr) { + mPtr->Mark(); + } + } + + private: + T* mPtr; +}; + +using AutoMarkingWrappedNativePtr = TypedAutoMarkingPtr<XPCWrappedNative>; +using AutoMarkingWrappedNativeTearOffPtr = + TypedAutoMarkingPtr<XPCWrappedNativeTearOff>; +using AutoMarkingWrappedNativeProtoPtr = + TypedAutoMarkingPtr<XPCWrappedNativeProto>; + +/***************************************************************************/ +// Definitions in XPCVariant.cpp. + +// {1809FD50-91E8-11d5-90F9-0010A4E73D9A} +#define XPCVARIANT_IID \ + { \ + 0x1809fd50, 0x91e8, 0x11d5, { \ + 0x90, 0xf9, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a \ + } \ + } + +// {DC524540-487E-4501-9AC7-AAA784B17C1C} +#define XPCVARIANT_CID \ + { \ + 0xdc524540, 0x487e, 0x4501, { \ + 0x9a, 0xc7, 0xaa, 0xa7, 0x84, 0xb1, 0x7c, 0x1c \ + } \ + } + +class XPCVariant : public nsIVariant { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIVARIANT + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(XPCVariant) + + // If this class ever implements nsIWritableVariant, take special care with + // the case when mJSVal is JSVAL_STRING, since we don't own the data in + // that case. + + // We #define and iid so that out module local code can use QI to detect + // if a given nsIVariant is in fact an XPCVariant. + NS_DECLARE_STATIC_IID_ACCESSOR(XPCVARIANT_IID) + + static already_AddRefed<XPCVariant> newVariant(JSContext* cx, + const JS::Value& aJSVal); + + /** + * This getter clears the gray bit before handing out the Value if the Value + * represents a JSObject. That means that the object is guaranteed to be + * kept alive past the next CC. + */ + JS::Value GetJSVal() const { return mJSVal; } + + protected: + /** + * This getter does not change the color of the Value (if it represents a + * JSObject) meaning that the value returned is not guaranteed to be kept + * alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JS::Value GetJSValPreserveColor() const { return mJSVal.unbarrieredGet(); } + + XPCVariant(JSContext* cx, const JS::Value& aJSVal); + + public: + /** + * Convert a variant into a JS::Value. + * + * @param cx the context for the whole procedure + * @param variant the variant to convert + * @param scope the default scope to put on the new JSObject's parent chain + * @param pErr [out] relevant error code, if any. + * @param pJSVal [out] the resulting jsval. + */ + static bool VariantDataToJS(JSContext* cx, nsIVariant* variant, + nsresult* pErr, JS::MutableHandleValue pJSVal); + + protected: + virtual ~XPCVariant(); + + bool InitializeData(JSContext* cx); + + void Cleanup(); + + nsDiscriminatedUnion mData; + JS::Heap<JS::Value> mJSVal; + bool mReturnRawObject; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(XPCVariant, XPCVARIANT_IID) + +/***************************************************************************/ +// Utilities + +inline JSContext* xpc_GetSafeJSContext() { + return XPCJSContext::Get()->Context(); +} + +namespace xpc { + +// JSNatives to expose atob and btoa in various non-DOM XPConnect scopes. +bool Atob(JSContext* cx, unsigned argc, JS::Value* vp); + +bool Btoa(JSContext* cx, unsigned argc, JS::Value* vp); + +// Helper function that creates a JSFunction that wraps a native function that +// forwards the call to the original 'callable'. +class FunctionForwarderOptions; +bool NewFunctionForwarder(JSContext* cx, JS::HandleId id, + JS::HandleObject callable, + FunctionForwarderOptions& options, + JS::MutableHandleValue vp); + +// Old fashioned xpc error reporter. Try to use JS_ReportError instead. +nsresult ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval); + +struct GlobalProperties { + GlobalProperties() { mozilla::PodZero(this); } + bool Parse(JSContext* cx, JS::HandleObject obj); + bool DefineInXPCComponents(JSContext* cx, JS::HandleObject obj); + bool DefineInSandbox(JSContext* cx, JS::HandleObject obj); + + // Interface objects we can expose. + bool AbortController : 1; + bool Blob : 1; + bool ChromeUtils : 1; + bool CSS : 1; + bool CSSRule : 1; + bool Directory : 1; + bool Document : 1; + bool DOMException : 1; + bool DOMParser : 1; + bool DOMTokenList : 1; + bool Element : 1; + bool Event : 1; + bool File : 1; + bool FileReader : 1; + bool FormData : 1; + bool Headers : 1; + bool IOUtils : 1; + bool InspectorUtils : 1; + bool MessageChannel : 1; + bool MIDIInputMap : 1; + bool MIDIOutputMap : 1; + bool Node : 1; + bool NodeFilter : 1; + bool PathUtils : 1; + bool Performance : 1; + bool PromiseDebugging : 1; + bool Range : 1; + bool Selection : 1; + bool TextDecoder : 1; + bool TextEncoder : 1; + bool URL : 1; + bool URLSearchParams : 1; + bool XMLHttpRequest : 1; + bool WebSocket : 1; + bool Window : 1; + bool XMLSerializer : 1; + bool ReadableStream : 1; + + // Ad-hoc property names we implement. + bool atob : 1; + bool btoa : 1; + bool caches : 1; + bool crypto : 1; + bool fetch : 1; + bool storage : 1; + bool structuredClone : 1; + bool indexedDB : 1; + bool isSecureContext : 1; + bool rtcIdentityProvider : 1; + + private: + bool Define(JSContext* cx, JS::HandleObject obj); +}; + +// Infallible. +already_AddRefed<nsIXPCComponents_utils_Sandbox> NewSandboxConstructor(); + +// Returns true if class of 'obj' is SandboxClass. +bool IsSandbox(JSObject* obj); + +class MOZ_STACK_CLASS OptionsBase { + public: + explicit OptionsBase(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : mCx(cx), mObject(cx, options) {} + + virtual bool Parse() = 0; + + protected: + bool ParseValue(const char* name, JS::MutableHandleValue prop, + bool* found = nullptr); + bool ParseBoolean(const char* name, bool* prop); + bool ParseObject(const char* name, JS::MutableHandleObject prop); + bool ParseJSString(const char* name, JS::MutableHandleString prop); + bool ParseString(const char* name, nsCString& prop); + bool ParseString(const char* name, nsString& prop); + bool ParseId(const char* name, JS::MutableHandleId id); + bool ParseUInt32(const char* name, uint32_t* prop); + + JSContext* mCx; + JS::RootedObject mObject; +}; + +class MOZ_STACK_CLASS SandboxOptions : public OptionsBase { + public: + explicit SandboxOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options), + wantXrays(true), + allowWaivers(true), + wantComponents(true), + wantExportHelpers(false), + isWebExtensionContentScript(false), + proto(cx), + sameZoneAs(cx), + forceSecureContext(false), + freshCompartment(false), + freshZone(false), + isUAWidgetScope(false), + invisibleToDebugger(false), + discardSource(false), + metadata(cx), + userContextId(0), + originAttributes(cx) {} + + virtual bool Parse() override; + + bool wantXrays; + bool allowWaivers; + bool wantComponents; + bool wantExportHelpers; + bool isWebExtensionContentScript; + JS::RootedObject proto; + nsCString sandboxName; + JS::RootedObject sameZoneAs; + bool forceSecureContext; + bool freshCompartment; + bool freshZone; + bool isUAWidgetScope; + bool invisibleToDebugger; + bool discardSource; + GlobalProperties globalProperties; + JS::RootedValue metadata; + uint32_t userContextId; + JS::RootedObject originAttributes; + + protected: + bool ParseGlobalProperties(); +}; + +class MOZ_STACK_CLASS CreateObjectInOptions : public OptionsBase { + public: + explicit CreateObjectInOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options), defineAs(cx, JS::PropertyKey::Void()) {} + + virtual bool Parse() override { return ParseId("defineAs", &defineAs); } + + JS::RootedId defineAs; +}; + +class MOZ_STACK_CLASS ExportFunctionOptions : public OptionsBase { + public: + explicit ExportFunctionOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options), + defineAs(cx, JS::PropertyKey::Void()), + allowCrossOriginArguments(false) {} + + virtual bool Parse() override { + return ParseId("defineAs", &defineAs) && + ParseBoolean("allowCrossOriginArguments", + &allowCrossOriginArguments); + } + + JS::RootedId defineAs; + bool allowCrossOriginArguments; +}; + +class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase { + public: + explicit FunctionForwarderOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options), allowCrossOriginArguments(false) {} + + JSObject* ToJSObject(JSContext* cx) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); + if (!obj) { + return nullptr; + } + + JS::RootedValue val(cx); + unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT; + val = JS::BooleanValue(allowCrossOriginArguments); + if (!JS_DefineProperty(cx, obj, "allowCrossOriginArguments", val, attrs)) { + return nullptr; + } + + return obj; + } + + virtual bool Parse() override { + return ParseBoolean("allowCrossOriginArguments", + &allowCrossOriginArguments); + } + + bool allowCrossOriginArguments; +}; + +class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase { + public: + explicit StackScopedCloneOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options), + wrapReflectors(false), + cloneFunctions(false), + deepFreeze(false) {} + + virtual bool Parse() override { + return ParseBoolean("wrapReflectors", &wrapReflectors) && + ParseBoolean("cloneFunctions", &cloneFunctions) && + ParseBoolean("deepFreeze", &deepFreeze); + } + + // When a reflector is encountered, wrap it rather than aborting the clone. + bool wrapReflectors; + + // When a function is encountered, clone it (exportFunction-style) rather than + // aborting the clone. + bool cloneFunctions; + + // If true, the resulting object is deep-frozen after being cloned. + bool deepFreeze; +}; + +JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp, + nsIPrincipal* principal, + JS::RealmOptions& aOptions); + +// Modify the provided compartment options, consistent with |aPrincipal| and +// with globally-cached values of various preferences. +// +// Call this function *before* |aOptions| is used to create the corresponding +// global object, as not all of the options it sets can be modified on an +// existing global object. (The type system should make this obvious, because +// you can't get a *mutable* JS::RealmOptions& from an existing global +// object.) +void InitGlobalObjectOptions(JS::RealmOptions& aOptions, + bool aIsSystemPrincipal, + bool aShouldResistFingerprinting); + +// Finish initializing an already-created, not-yet-exposed-to-script global +// object. This will attach a Components object (if necessary) and call +// |JS_FireOnNewGlobalObject| (if necessary). +// +// If you must modify compartment options, see InitGlobalObjectOptions above. +bool InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal, + uint32_t aFlags); + +// Helper for creating a sandbox object to use for evaluating +// untrusted code completely separated from all other code in the +// system using EvalInSandbox(). Takes the JSContext on which to +// do setup etc on, puts the sandbox object in *vp (which must be +// rooted by the caller), and uses the principal that's either +// directly passed in prinOrSop or indirectly as an +// nsIScriptObjectPrincipal holding the principal. If no principal is +// reachable through prinOrSop, a new null principal will be created +// and used. +nsresult CreateSandboxObject(JSContext* cx, JS::MutableHandleValue vp, + nsISupports* prinOrSop, + xpc::SandboxOptions& options); +// Helper for evaluating scripts in a sandbox object created with +// CreateSandboxObject(). The caller is responsible of ensuring +// that *rval doesn't get collected during the call or usage after the +// call. This helper will use filename and lineNo for error reporting, +// and if no filename is provided it will use the codebase from the +// principal and line number 1 as a fallback. +nsresult EvalInSandbox(JSContext* cx, JS::HandleObject sandbox, + const nsAString& source, const nsACString& filename, + int32_t lineNo, bool enforceFilenameRestrictions, + JS::MutableHandleValue rval); + +// Helper for retrieving metadata stored in a reserved slot. The metadata +// is set during the sandbox creation using the "metadata" option. +nsresult GetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, + JS::MutableHandleValue rval); + +nsresult SetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, + JS::HandleValue metadata); + +bool CreateObjectIn(JSContext* cx, JS::HandleValue vobj, + CreateObjectInOptions& options, + JS::MutableHandleValue rval); + +bool EvalInWindow(JSContext* cx, const nsAString& source, + JS::HandleObject scope, JS::MutableHandleValue rval); + +bool ExportFunction(JSContext* cx, JS::HandleValue vscope, + JS::HandleValue vfunction, JS::HandleValue voptions, + JS::MutableHandleValue rval); + +bool CloneInto(JSContext* cx, JS::HandleValue vobj, JS::HandleValue vscope, + JS::HandleValue voptions, JS::MutableHandleValue rval); + +bool StackScopedClone(JSContext* cx, StackScopedCloneOptions& options, + JS::HandleObject sourceScope, JS::MutableHandleValue val); + +} /* namespace xpc */ + +/***************************************************************************/ +// Inlined utilities. + +inline bool xpc_ForcePropertyResolve(JSContext* cx, JS::HandleObject obj, + jsid id); + +inline jsid GetJSIDByIndex(JSContext* cx, unsigned index); + +namespace xpc { + +enum WrapperDenialType { + WrapperDenialForXray = 0, + WrapperDenialForCOW, + WrapperDenialTypeCount +}; +bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, + const char* reason); + +class CompartmentOriginInfo { + public: + CompartmentOriginInfo(const CompartmentOriginInfo&) = delete; + + CompartmentOriginInfo(mozilla::BasePrincipal* aOrigin, + const mozilla::SiteIdentifier& aSite) + : mOrigin(aOrigin), mSite(aSite) { + MOZ_ASSERT(aOrigin); + MOZ_ASSERT(aSite.IsInitialized()); + } + + bool IsSameOrigin(nsIPrincipal* aOther) const; + + // Does the principal of compartment a subsume the principal of compartment b? + static bool Subsumes(JS::Compartment* aCompA, JS::Compartment* aCompB); + static bool SubsumesIgnoringFPD(JS::Compartment* aCompA, + JS::Compartment* aCompB); + + bool MightBeWebContent() const; + + // Note: this principal must not be used for subsumes/equality checks + // considering document.domain. See mOrigin. + mozilla::BasePrincipal* GetPrincipalIgnoringDocumentDomain() const { + return mOrigin; + } + + const mozilla::SiteIdentifier& SiteRef() const { return mSite; } + + bool HasChangedDocumentDomain() const { return mChangedDocumentDomain; } + void SetChangedDocumentDomain() { mChangedDocumentDomain = true; } + + private: + // All globals in the compartment must have this origin. Note that + // individual globals and principals can have their domain changed via + // document.domain, so this principal must not be used for things like + // subsumesConsideringDomain or equalsConsideringDomain. Use the realm's + // principal for that. + RefPtr<mozilla::BasePrincipal> mOrigin; + + // In addition to the origin we also store the SiteIdentifier. When realms + // in different compartments can become same-origin (via document.domain), + // these compartments must have equal SiteIdentifiers. (This is derived from + // mOrigin but we cache it here for performance reasons.) + mozilla::SiteIdentifier mSite; + + // True if any global in this compartment mutated document.domain. + bool mChangedDocumentDomain = false; +}; + +// The CompartmentPrivate contains XPConnect-specific stuff related to each JS +// compartment. Since compartments are trust domains, this means mostly +// information needed to select the right security policy for cross-compartment +// wrappers. +class CompartmentPrivate { + CompartmentPrivate() = delete; + CompartmentPrivate(const CompartmentPrivate&) = delete; + + public: + CompartmentPrivate(JS::Compartment* c, + mozilla::UniquePtr<XPCWrappedNativeScope> scope, + mozilla::BasePrincipal* origin, + const mozilla::SiteIdentifier& site); + + ~CompartmentPrivate(); + + static CompartmentPrivate* Get(JS::Compartment* compartment) { + MOZ_ASSERT(compartment); + void* priv = JS_GetCompartmentPrivate(compartment); + return static_cast<CompartmentPrivate*>(priv); + } + + static CompartmentPrivate* Get(JS::Realm* realm) { + MOZ_ASSERT(realm); + JS::Compartment* compartment = JS::GetCompartmentForRealm(realm); + return Get(compartment); + } + + static CompartmentPrivate* Get(JSObject* object) { + JS::Compartment* compartment = JS::GetCompartment(object); + return Get(compartment); + } + + bool CanShareCompartmentWith(nsIPrincipal* principal) { + // Only share if we're same-origin with the principal. + if (!originInfo.IsSameOrigin(principal)) { + return false; + } + + // Don't share if we have any weird state set. + return !wantXrays && !isWebExtensionContentScript && + !isUAWidgetCompartment && mScope->XBLScopeStateMatches(principal); + } + + CompartmentOriginInfo originInfo; + + // Controls whether this compartment gets Xrays to same-origin. This behavior + // is deprecated, but is still the default for sandboxes for compatibity + // reasons. + bool wantXrays; + + // Controls whether this compartment is allowed to waive Xrays to content + // that it subsumes. This should generally be true, except in cases where we + // want to prevent code from depending on Xray Waivers (which might make it + // more portable to other browser architectures). + bool allowWaivers; + + // This compartment corresponds to a WebExtension content script, and + // receives various bits of special compatibility behavior. + bool isWebExtensionContentScript; + + // True if this compartment is a UA widget compartment. + bool isUAWidgetCompartment; + + // See CompartmentHasExclusiveExpandos. + bool hasExclusiveExpandos; + + // Whether SystemIsBeingShutDown has been called on this compartment. + bool wasShutdown; + + JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap.get(); } + void UpdateWeakPointersAfterGC(JSTracer* trc); + + void SystemIsBeingShutDown(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + struct MapEntryGCPolicy { + static bool traceWeak(JSTracer* trc, const void* /* unused */, + JS::Heap<JSObject*>* value) { + return JS::GCPolicy<JS::Heap<JSObject*>>::traceWeak(trc, value); + } + }; + + typedef JS::GCHashMap<const void*, JS::Heap<JSObject*>, + mozilla::PointerHasher<const void*>, + js::SystemAllocPolicy, MapEntryGCPolicy> + RemoteProxyMap; + RemoteProxyMap& GetRemoteProxyMap() { return mRemoteProxies; } + + XPCWrappedNativeScope* GetScope() { return mScope.get(); } + + private: + mozilla::UniquePtr<JSObject2WrappedJSMap> mWrappedJSMap; + + // Cache holding proxy objects for Window objects (and their Location object) + // that are loaded in a different process. + RemoteProxyMap mRemoteProxies; + + // Our XPCWrappedNativeScope. + mozilla::UniquePtr<XPCWrappedNativeScope> mScope; +}; + +inline void CrashIfNotInAutomation() { MOZ_RELEASE_ASSERT(IsInAutomation()); } + +// XPConnect-specific data associated with each JavaScript realm. Per-Window +// settings live here; security-wrapper-related settings live in the +// CompartmentPrivate. +// +// Following the ECMAScript spec, a realm contains a global (e.g. an inner +// Window) and its associated scripts and objects; a compartment may contain +// several same-origin realms. +class RealmPrivate { + RealmPrivate() = delete; + RealmPrivate(const RealmPrivate&) = delete; + + public: + enum LocationHint { LocationHintRegular, LocationHintAddon }; + + explicit RealmPrivate(JS::Realm* realm); + + // Creates the RealmPrivate and CompartmentPrivate (if needed) for a new + // global. + static void Init(JS::HandleObject aGlobal, + const mozilla::SiteIdentifier& aSite); + + static RealmPrivate* Get(JS::Realm* realm) { + MOZ_ASSERT(realm); + void* priv = JS::GetRealmPrivate(realm); + return static_cast<RealmPrivate*>(priv); + } + + // Get the RealmPrivate for a given object. `object` must not be a + // cross-compartment wrapper, as CCWs aren't dedicated to a particular + // realm. + static RealmPrivate* Get(JSObject* object) { + JS::Realm* realm = JS::GetObjectRealmOrNull(object); + return Get(realm); + } + + // The scriptability of this realm. + Scriptability scriptability; + + // Whether we've emitted a warning about a property that was filtered out + // by a security wrapper. See XrayWrapper.cpp. + bool wrapperDenialWarnings[WrapperDenialTypeCount]; + + const nsACString& GetLocation() { + if (location.IsEmpty() && locationURI) { + nsCOMPtr<nsIXPConnectWrappedJS> jsLocationURI = + do_QueryInterface(locationURI); + if (jsLocationURI) { + // We cannot call into JS-implemented nsIURI objects, because + // we are iterating over the JS heap at this point. + location = "<JS-implemented nsIURI location>"_ns; + } else if (NS_FAILED(locationURI->GetSpec(location))) { + location = "<unknown location>"_ns; + } + } + return location; + } + bool GetLocationURI(LocationHint aLocationHint, nsIURI** aURI) { + if (locationURI) { + nsCOMPtr<nsIURI> rval = locationURI; + rval.forget(aURI); + return true; + } + return TryParseLocationURI(aLocationHint, aURI); + } + bool GetLocationURI(nsIURI** aURI) { + return GetLocationURI(LocationHintRegular, aURI); + } + + void SetLocation(const nsACString& aLocation) { + if (aLocation.IsEmpty()) { + return; + } + if (!location.IsEmpty() || locationURI) { + return; + } + location = aLocation; + } + void SetLocationURI(nsIURI* aLocationURI) { + if (!aLocationURI) { + return; + } + if (locationURI) { + return; + } + locationURI = aLocationURI; + } + + // JSStackFrames are tracked on a per-realm basis so they + // can be cleared when the associated window goes away. + void RegisterStackFrame(JSStackFrameBase* aFrame); + void UnregisterStackFrame(JSStackFrameBase* aFrame); + void NukeJSStackFrames(); + + private: + nsCString location; + nsCOMPtr<nsIURI> locationURI; + + bool TryParseLocationURI(LocationHint aType, nsIURI** aURI); + + nsTHashtable<nsPtrHashKey<JSStackFrameBase>> mJSStackFrames; +}; + +inline XPCWrappedNativeScope* ObjectScope(JSObject* obj) { + return CompartmentPrivate::Get(obj)->GetScope(); +} + +JSObject* NewOutObject(JSContext* cx); +bool IsOutObject(JSContext* cx, JSObject* obj); + +nsresult HasInstance(JSContext* cx, JS::HandleObject objArg, const nsID* iid, + bool* bp); + +// Returns the principal associated with |obj|'s realm. The object must not be a +// cross-compartment wrapper. +nsIPrincipal* GetObjectPrincipal(JSObject* obj); + +// Attempt to clean up the passed in value pointer. The pointer `value` must be +// a pointer to a value described by the type `nsXPTType`. +// +// This method expects a value of the following types: +// TD_NSIDPTR +// value : nsID* (free) +// TD_ASTRING, TD_CSTRING, TD_UTF8STRING +// value : ns[C]String* (truncate) +// TD_PSTRING, TD_PWSTRING, TD_PSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS +// value : char[16_t]** (free) +// TD_INTERFACE_TYPE, TD_INTERFACE_IS_TYPE +// value : nsISupports** (release) +// TD_LEGACY_ARRAY (NOTE: aArrayLen should be passed) +// value : void** (destroy elements & free) +// TD_ARRAY +// value : nsTArray<T>* (destroy elements & Clear) +// TD_DOMOBJECT +// value : T** (cleanup) +// TD_PROMISE +// value : dom::Promise** (release) +// +// Other types are ignored. +inline void CleanupValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen = 0); + +// Out-of-line internals for xpc::CleanupValue. Defined in XPCConvert.cpp. +void InnerCleanupValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen); + +// In order to be able to safely call CleanupValue on a generated value, the +// data behind it needs to be initialized to a safe value. This method handles +// initializing the backing data to a safe value to use as an argument to +// XPCConvert methods, or xpc::CleanupValue. +// +// The pointer `aValue` must point to a block of memory at least aType.Stride() +// bytes large, and correctly aligned. +// +// This method accepts the same types as xpc::CleanupValue. +void InitializeValue(const nsXPTType& aType, void* aValue); + +// If a value was initialized with InitializeValue, it should be destroyed with +// DestructValue. This method acts like CleanupValue, except that destructors +// for complex types are also invoked, leaving them in an invalid state. +// +// This method should be called when destroying types initialized with +// InitializeValue. +// +// The pointer 'aValue' must point to a valid value of type 'aType'. +void DestructValue(const nsXPTType& aType, void* aValue, + uint32_t aArrayLen = 0); + +bool SandboxCreateCrypto(JSContext* cx, JS::Handle<JSObject*> obj); +bool SandboxCreateFetch(JSContext* cx, JS::Handle<JSObject*> obj); +bool SandboxCreateStructuredClone(JSContext* cx, JS::Handle<JSObject*> obj); + +} // namespace xpc + +namespace mozilla { +namespace dom { +extern bool DefineStaticJSVals(JSContext* cx); +} // namespace dom +} // namespace mozilla + +bool xpc_LocalizeRuntime(JSRuntime* rt); +void xpc_DelocalizeRuntime(JSRuntime* rt); + +/***************************************************************************/ +// Inlines use the above - include last. + +#include "XPCInlines.h" + +/***************************************************************************/ + +#endif /* xpcprivate_h___ */ diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h new file mode 100644 index 0000000000..970912aad7 --- /dev/null +++ b/js/xpconnect/src/xpcpublic.h @@ -0,0 +1,835 @@ +/* -*- 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/. */ + +#ifndef xpcpublic_h +#define xpcpublic_h + +#include <cstddef> +#include <cstdint> +#include "ErrorList.h" +#include "js/BuildId.h" +#include "js/ErrorReport.h" +#include "js/GCAPI.h" +#include "js/Object.h" +#include "js/RootingAPI.h" +#include "js/String.h" +#include "js/TypeDecls.h" +#include "js/Utility.h" +#include "js/Value.h" +#include "jsapi.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/fallible.h" +#include "nsAtom.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIURI.h" +#include "nsStringBuffer.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" + +// XXX only for NukeAllWrappersForRealm, which is only used in +// dom/base/WindowDestroyedEvent.cpp outside of js +#include "jsfriendapi.h" + +class JSObject; +class JSString; +class JSTracer; +class nsGlobalWindowInner; +class nsIGlobalObject; +class nsIHandleReportCallback; +class nsIPrincipal; +class nsPIDOMWindowInner; +struct JSContext; +struct nsID; +struct nsXPTInterfaceInfo; + +namespace JS { +class Compartment; +class ContextOptions; +class Realm; +class RealmOptions; +class Value; +struct RuntimeStats; +} // namespace JS + +namespace mozilla { +class BasePrincipal; + +namespace dom { +class Exception; +} // namespace dom +} // namespace mozilla + +using xpcGCCallback = void (*)(JSGCStatus); + +namespace xpc { + +class Scriptability { + public: + explicit Scriptability(JS::Realm* realm); + bool Allowed(); + bool IsImmuneToScriptPolicy(); + + void Block(); + void Unblock(); + void SetWindowAllowsScript(bool aAllowed); + + static Scriptability& Get(JSObject* aScope); + + // Returns true if scripting is allowed, false otherwise (if no Scriptability + // exists, like for example inside a ShadowRealm global, then script execution + // is assumed to be allowed) + static bool AllowedIfExists(JSObject* aScope); + + private: + // Whenever a consumer wishes to prevent script from running on a global, + // it increments this value with a call to Block(). When it wishes to + // re-enable it (if ever), it decrements this value with a call to Unblock(). + // Script may not run if this value is non-zero. + uint32_t mScriptBlocks; + + // Whether the DOM window allows javascript in this scope. If this scope + // doesn't have a window, this value is always true. + bool mWindowAllowsScript; + + // Whether this scope is immune to user-defined or addon-defined script + // policy. + bool mImmuneToScriptPolicy; + + // Whether the new-style domain policy when this compartment was created + // forbids script execution. + bool mScriptBlockedByPolicy; +}; + +JSObject* TransplantObject(JSContext* cx, JS::Handle<JSObject*> origobj, + JS::Handle<JSObject*> target); + +JSObject* TransplantObjectRetainingXrayExpandos(JSContext* cx, + JS::Handle<JSObject*> origobj, + JS::Handle<JSObject*> target); + +// If origObj has an xray waiver, nuke it before transplant. +JSObject* TransplantObjectNukingXrayWaiver(JSContext* cx, + JS::Handle<JSObject*> origObj, + JS::Handle<JSObject*> target); + +bool IsUAWidgetCompartment(JS::Compartment* compartment); +bool IsUAWidgetScope(JS::Realm* realm); +bool IsInUAWidgetScope(JSObject* obj); + +bool MightBeWebContentCompartment(JS::Compartment* compartment); + +void SetCompartmentChangedDocumentDomain(JS::Compartment* compartment); + +JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal); + +JSObject* GetUAWidgetScope(JSContext* cx, JSObject* contentScope); + +// Returns whether XBL scopes have been explicitly disabled for code running +// in this compartment. See the comment around mAllowContentXBLScope. +bool AllowContentXBLScope(JS::Realm* realm); + +// Get the scope for creating reflectors for native anonymous content +// whose normal global would be the given global. +JSObject* NACScope(JSObject* global); + +bool IsSandboxPrototypeProxy(JSObject* obj); +bool IsWebExtensionContentScriptSandbox(JSObject* obj); + +// The JSContext argument represents the Realm that's asking the question. This +// is needed to properly answer without exposing information unnecessarily +// from behind security wrappers. There will be no exceptions thrown on this +// JSContext. +bool IsReflector(JSObject* obj, JSContext* cx); + +bool IsXrayWrapper(JSObject* obj); + +// If this function was created for a given XrayWrapper, returns the global of +// the Xrayed object. Otherwise, returns the global of the function. +// +// To emphasize the obvious: the return value here is not necessarily same- +// compartment with the argument. +JSObject* XrayAwareCalleeGlobal(JSObject* fun); + +void TraceXPCGlobal(JSTracer* trc, JSObject* obj); + +/** + * Creates a new global object using the given aCOMObj as the global + * object. The object will be set up according to the flags (defined + * below). If you do not pass INIT_JS_STANDARD_CLASSES, then aCOMObj + * must implement nsIXPCScriptable so it can resolve the standard + * classes when asked by the JS engine. + * + * @param aJSContext the context to use while creating the global object. + * @param aCOMObj the native object that represents the global object. + * @param aPrincipal the principal of the code that will run in this + * compartment. Can be null if not on the main thread. + * @param aFlags one of the flags below specifying what options this + * global object wants. + * @param aOptions JSAPI-specific options for the new compartment. + */ +nsresult InitClassesWithNewWrappedGlobal( + JSContext* aJSContext, nsISupports* aCOMObj, nsIPrincipal* aPrincipal, + uint32_t aFlags, JS::RealmOptions& aOptions, + JS::MutableHandle<JSObject*> aNewGlobal); + +enum InitClassesFlag { + INIT_JS_STANDARD_CLASSES = 1 << 0, + DONT_FIRE_ONNEWGLOBALHOOK = 1 << 1, + OMIT_COMPONENTS_OBJECT = 1 << 2, +}; + +} /* namespace xpc */ + +namespace JS { + +struct RuntimeStats; + +} // namespace JS + +static_assert(JSCLASS_GLOBAL_APPLICATION_SLOTS > 0, + "Need at least one slot for JSCLASS_SLOT0_IS_NSISUPPORTS"); + +#define XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(n) \ + JSCLASS_DOM_GLOBAL | JSCLASS_SLOT0_IS_NSISUPPORTS | \ + JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS + n) + +#define XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET \ + (JSCLASS_GLOBAL_SLOT_COUNT + DOM_GLOBAL_SLOTS) + +#define XPCONNECT_GLOBAL_FLAGS XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(0) + +inline JSObject* xpc_FastGetCachedWrapper(JSContext* cx, nsWrapperCache* cache, + JS::MutableHandle<JS::Value> vp) { + if (cache) { + JSObject* wrapper = cache->GetWrapper(); + if (wrapper && + JS::GetCompartment(wrapper) == js::GetContextCompartment(cx)) { + vp.setObject(*wrapper); + return wrapper; + } + } + + return nullptr; +} + +// If aWrappedJS is a JS wrapper, unmark its JSObject. +extern void xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS); + +extern void xpc_UnmarkSkippableJSHolders(); + +// Defined in XPCDebug.cpp. +extern bool xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps); + +// Return a newly-allocated string containing a representation of the +// current JS stack. Defined in XPCDebug.cpp. +extern JS::UniqueChars xpc_PrintJSStack(JSContext* cx, bool showArgs, + bool showLocals, bool showThisProps); + +// readable string conversions, static methods and members only +class XPCStringConvert { + public: + // If the string shares the readable's buffer, that buffer will + // get assigned to *sharedBuffer. Otherwise null will be + // assigned. + static bool ReadableToJSVal(JSContext* cx, const nsAString& readable, + nsStringBuffer** sharedBuffer, + JS::MutableHandle<JS::Value> vp); + + // Convert the given stringbuffer/length pair to a jsval + static MOZ_ALWAYS_INLINE bool StringBufferToJSVal( + JSContext* cx, nsStringBuffer* buf, uint32_t length, + JS::MutableHandle<JS::Value> rval, bool* sharedBuffer) { + JSString* str = JS_NewMaybeExternalString( + cx, static_cast<char16_t*>(buf->Data()), length, + &sDOMStringExternalString, sharedBuffer); + if (!str) { + return false; + } + rval.setString(str); + return true; + } + + static inline bool StringLiteralToJSVal(JSContext* cx, + const char16_t* literal, + uint32_t length, + JS::MutableHandle<JS::Value> rval) { + bool ignored; + JSString* str = JS_NewMaybeExternalString( + cx, literal, length, &sLiteralExternalString, &ignored); + if (!str) { + return false; + } + rval.setString(str); + return true; + } + + static inline bool DynamicAtomToJSVal(JSContext* cx, nsDynamicAtom* atom, + JS::MutableHandle<JS::Value> rval) { + bool sharedAtom; + JSString* str = + JS_NewMaybeExternalString(cx, atom->GetUTF16String(), atom->GetLength(), + &sDynamicAtomExternalString, &sharedAtom); + if (!str) { + return false; + } + if (sharedAtom) { + // We only have non-owning atoms in DOMString for now. + // nsDynamicAtom::AddRef is always-inline and defined in a + // translation unit we can't get to here. So we need to go through + // nsAtom::AddRef to call it. + static_cast<nsAtom*>(atom)->AddRef(); + } + rval.setString(str); + return true; + } + + static MOZ_ALWAYS_INLINE bool MaybeGetExternalStringChars( + JSString* str, const JSExternalStringCallbacks* desiredCallbacks, + const char16_t** chars) { + const JSExternalStringCallbacks* callbacks; + return JS::IsExternalString(str, &callbacks, chars) && + callbacks == desiredCallbacks; + } + + // Returns non-null chars if the given string is a literal external string. + static MOZ_ALWAYS_INLINE bool MaybeGetLiteralStringChars( + JSString* str, const char16_t** chars) { + return MaybeGetExternalStringChars(str, &sLiteralExternalString, chars); + } + + // Returns non-null chars if the given string is a DOM external string. + static MOZ_ALWAYS_INLINE bool MaybeGetDOMStringChars(JSString* str, + const char16_t** chars) { + return MaybeGetExternalStringChars(str, &sDOMStringExternalString, chars); + } + + private: + struct LiteralExternalString : public JSExternalStringCallbacks { + void finalize(char16_t* aChars) const override; + size_t sizeOfBuffer(const char16_t* aChars, + mozilla::MallocSizeOf aMallocSizeOf) const override; + }; + struct DOMStringExternalString : public JSExternalStringCallbacks { + void finalize(char16_t* aChars) const override; + size_t sizeOfBuffer(const char16_t* aChars, + mozilla::MallocSizeOf aMallocSizeOf) const override; + }; + struct DynamicAtomExternalString : public JSExternalStringCallbacks { + void finalize(char16_t* aChars) const override; + size_t sizeOfBuffer(const char16_t* aChars, + mozilla::MallocSizeOf aMallocSizeOf) const override; + }; + static const LiteralExternalString sLiteralExternalString; + static const DOMStringExternalString sDOMStringExternalString; + static const DynamicAtomExternalString sDynamicAtomExternalString; + + XPCStringConvert() = delete; +}; + +namespace xpc { + +// If these functions return false, then an exception will be set on cx. +bool Base64Encode(JSContext* cx, JS::Handle<JS::Value> val, + JS::MutableHandle<JS::Value> out); +bool Base64Decode(JSContext* cx, JS::Handle<JS::Value> val, + JS::MutableHandle<JS::Value> out); + +/** + * Convert an nsString to jsval, returning true on success. + * Note, the ownership of the string buffer may be moved from str to rval. + * If that happens, str will point to an empty string after this call. + */ +bool NonVoidStringToJsval(JSContext* cx, nsAString& str, + JS::MutableHandle<JS::Value> rval); +inline bool StringToJsval(JSContext* cx, nsAString& str, + JS::MutableHandle<JS::Value> rval) { + // From the T_ASTRING case in XPCConvert::NativeData2JS. + if (str.IsVoid()) { + rval.setNull(); + return true; + } + return NonVoidStringToJsval(cx, str, rval); +} + +inline bool NonVoidStringToJsval(JSContext* cx, const nsAString& str, + JS::MutableHandle<JS::Value> rval) { + nsString mutableCopy; + if (!mutableCopy.Assign(str, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + return NonVoidStringToJsval(cx, mutableCopy, rval); +} + +inline bool StringToJsval(JSContext* cx, const nsAString& str, + JS::MutableHandle<JS::Value> rval) { + nsString mutableCopy; + if (!mutableCopy.Assign(str, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + return StringToJsval(cx, mutableCopy, rval); +} + +/** + * As above, but for mozilla::dom::DOMString. + */ +inline bool NonVoidStringToJsval(JSContext* cx, mozilla::dom::DOMString& str, + JS::MutableHandle<JS::Value> rval) { + if (str.IsEmpty()) { + rval.set(JS_GetEmptyStringValue(cx)); + return true; + } + + if (str.HasStringBuffer()) { + uint32_t length = str.StringBufferLength(); + nsStringBuffer* buf = str.StringBuffer(); + bool shared; + if (!XPCStringConvert::StringBufferToJSVal(cx, buf, length, rval, + &shared)) { + return false; + } + if (shared) { + // JS now needs to hold a reference to the buffer + str.RelinquishBufferOwnership(); + } + return true; + } + + if (str.HasLiteral()) { + return XPCStringConvert::StringLiteralToJSVal(cx, str.Literal(), + str.LiteralLength(), rval); + } + + if (str.HasAtom()) { + return XPCStringConvert::DynamicAtomToJSVal(cx, str.Atom(), rval); + } + + // It's an actual XPCOM string + return NonVoidStringToJsval(cx, str.AsAString(), rval); +} + +MOZ_ALWAYS_INLINE +bool StringToJsval(JSContext* cx, mozilla::dom::DOMString& str, + JS::MutableHandle<JS::Value> rval) { + if (str.IsNull()) { + rval.setNull(); + return true; + } + return NonVoidStringToJsval(cx, str, rval); +} + +mozilla::BasePrincipal* GetRealmPrincipal(JS::Realm* realm); + +void NukeAllWrappersForRealm(JSContext* cx, JS::Realm* realm, + js::NukeReferencesToWindow nukeReferencesToWindow = + js::NukeWindowReferences); + +void SetLocationForGlobal(JSObject* global, const nsACString& location); +void SetLocationForGlobal(JSObject* global, nsIURI* locationURI); + +// ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member +// of JS::ZoneStats. +class ZoneStatsExtras { + public: + ZoneStatsExtras() = default; + + nsCString pathPrefix; + + private: + ZoneStatsExtras(const ZoneStatsExtras& other) = delete; + ZoneStatsExtras& operator=(const ZoneStatsExtras& other) = delete; +}; + +// ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member +// of JS::RealmStats. +class RealmStatsExtras { + public: + RealmStatsExtras() = default; + + nsCString jsPathPrefix; + nsCString domPathPrefix; + nsCOMPtr<nsIURI> location; + + private: + RealmStatsExtras(const RealmStatsExtras& other) = delete; + RealmStatsExtras& operator=(const RealmStatsExtras& other) = delete; +}; + +// This reports all the stats in |rtStats| that belong in the "explicit" tree, +// (which isn't all of them). +// @see ZoneStatsExtras +// @see RealmStatsExtras +void ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, + const nsACString& rtPath, + nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize, + size_t* rtTotal = nullptr); + +/** + * Throws an exception on cx and returns false. + */ +bool Throw(JSContext* cx, nsresult rv); + +/** + * Returns the nsISupports native behind a given reflector (either DOM or + * XPCWN). If a non-reflector object is passed in, null will be returned. + * + * This function will not correctly handle Window or Location objects behind + * cross-compartment wrappers: it will return null. If you care about getting + * non-null for Window or Location, use ReflectorToISupportsDynamic. + */ +already_AddRefed<nsISupports> ReflectorToISupportsStatic(JSObject* reflector); + +/** + * Returns the nsISupports native behind a given reflector (either DOM or + * XPCWN). If a non-reflector object is passed in, null will be returned. + * + * The JSContext argument represents the Realm that's asking for the + * nsISupports. This is needed to properly handle Window and Location objects, + * which do dynamic security checks. + */ +already_AddRefed<nsISupports> ReflectorToISupportsDynamic(JSObject* reflector, + JSContext* cx); + +/** + * Singleton scopes for stuff that really doesn't fit anywhere else. + * + * If you find yourself wanting to use these compartments, you're probably doing + * something wrong. Callers MUST consult with the XPConnect module owner before + * using this compartment. If you don't, bholley will hunt you down. + */ +JSObject* UnprivilegedJunkScope(); + +JSObject* UnprivilegedJunkScope(const mozilla::fallible_t&); + +bool IsUnprivilegedJunkScope(JSObject*); + +/** + * This will generally be the shared JSM global, but callers should not depend + * on that fact. + */ +JSObject* PrivilegedJunkScope(); + +/** + * Shared compilation scope for XUL prototype documents and XBL + * precompilation. + */ +JSObject* CompilationScope(); + +/** + * Returns the nsIGlobalObject corresponding to |obj|'s JS global. |obj| must + * not be a cross-compartment wrapper: CCWs are not associated with a single + * global. + */ +nsIGlobalObject* NativeGlobal(JSObject* obj); + +/** + * Returns the nsIGlobalObject corresponding to |cx|'s JS global. Must not be + * called when |cx| is not in a Realm. + */ +nsIGlobalObject* CurrentNativeGlobal(JSContext* cx); + +/** + * If |aObj| is a window, returns the associated nsGlobalWindow. + * Otherwise, returns null. + */ +nsGlobalWindowInner* WindowOrNull(JSObject* aObj); + +/** + * If |aObj| has a window for a global, returns the associated nsGlobalWindow. + * Otherwise, returns null. Note: aObj must not be a cross-compartment wrapper + * because CCWs are not associated with a single global/realm. + */ +nsGlobalWindowInner* WindowGlobalOrNull(JSObject* aObj); + +/** + * If |aObj| is a Sandbox object associated with a DOMWindow via a + * sandboxPrototype, then return that DOMWindow. + * |aCx| is used for checked unwrapping of the Window. + */ +nsGlobalWindowInner* SandboxWindowOrNull(JSObject* aObj, JSContext* aCx); + +/** + * If |cx| is in a realm whose global is a window, returns the associated + * nsGlobalWindow. Otherwise, returns null. + */ +nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx); + +class MOZ_RAII AutoScriptActivity { + bool mActive; + bool mOldValue; + + public: + explicit AutoScriptActivity(bool aActive); + ~AutoScriptActivity(); +}; + +// This function may be used off-main-thread, in which case it is benignly +// racey. +bool ShouldDiscardSystemSource(); + +void SetPrefableRealmOptions(JS::RealmOptions& options); +void SetPrefableContextOptions(JS::ContextOptions& options); + +class ErrorBase { + public: + nsString mErrorMsg; + nsString mFileName; + uint32_t mSourceId; + uint32_t mLineNumber; + uint32_t mColumn; + + ErrorBase() : mSourceId(0), mLineNumber(0), mColumn(0) {} + + void Init(JSErrorBase* aReport); + + void AppendErrorDetailsTo(nsCString& error); +}; + +class ErrorNote : public ErrorBase { + public: + void Init(JSErrorNotes::Note* aNote); + + // Produce an error event message string from the given JSErrorNotes::Note. + // This may produce an empty string if aNote doesn't have a message + // attached. + static void ErrorNoteToMessageString(JSErrorNotes::Note* aNote, + nsAString& aString); + + // Log the error note to the stderr. + void LogToStderr(); +}; + +class ErrorReport : public ErrorBase { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ErrorReport); + + nsTArray<ErrorNote> mNotes; + + nsCString mCategory; + nsString mSourceLine; + nsString mErrorMsgName; + uint64_t mWindowID; + bool mIsWarning; + bool mIsMuted; + bool mIsPromiseRejection; + + ErrorReport() + : mWindowID(0), + mIsWarning(false), + mIsMuted(false), + mIsPromiseRejection(false) {} + + void Init(JSErrorReport* aReport, const char* aToStringResult, bool aIsChrome, + uint64_t aWindowID); + void Init(JSContext* aCx, mozilla::dom::Exception* aException, bool aIsChrome, + uint64_t aWindowID); + + // Log the error report to the console. Which console will depend on the + // window id it was initialized with. + void LogToConsole(); + // Log to console, using the given stack object (which should be a stack of + // the sort that JS::CaptureCurrentStack produces). aStack is allowed to be + // null. If aStack is non-null, aStackGlobal must be a non-null global + // object that's same-compartment with aStack. Note that aStack might be a + // CCW. + void LogToConsoleWithStack(nsGlobalWindowInner* aWin, + JS::Handle<mozilla::Maybe<JS::Value>> aException, + JS::Handle<JSObject*> aStack, + JS::Handle<JSObject*> aStackGlobal); + + // Produce an error event message string from the given JSErrorReport. Note + // that this may produce an empty string if aReport doesn't have a + // message attached. + static void ErrorReportToMessageString(JSErrorReport* aReport, + nsAString& aString); + + // Log the error report to the stderr. + void LogToStderr(); + + bool IsWarning() const { return mIsWarning; }; + + private: + ~ErrorReport() = default; +}; + +void DispatchScriptErrorEvent(nsPIDOMWindowInner* win, + JS::RootingContext* rootingCx, + xpc::ErrorReport* xpcReport, + JS::Handle<JS::Value> exception, + JS::Handle<JSObject*> exceptionStack); + +// Get a stack (as stackObj outparam) of the sort that can be passed to +// xpc::ErrorReport::LogToConsoleWithStack from the given exception value. Can +// be nullptr if the exception value doesn't have an associated stack, and if +// there is no stack supplied by the JS engine in exceptionStack. The +// returned stack, if any, may also not be in the same compartment as +// exceptionValue. +// +// The "win" argument passed in here should be the same as the window whose +// WindowID() is used to initialize the xpc::ErrorReport. This may be null, of +// course. If it's not null, this function may return a null stack object if +// the window is far enough gone, because in those cases we don't want to have +// the stack in the console message keeping the window alive. +// +// If this function sets stackObj to a non-null value, stackGlobal is set to +// either the JS exception object's global or the global of the SavedFrame we +// got from a DOM or XPConnect exception. In all cases, stackGlobal is an +// unwrapped global object and is same-compartment with stackObj. +void FindExceptionStackForConsoleReport( + nsPIDOMWindowInner* win, JS::Handle<JS::Value> exceptionValue, + JS::Handle<JSObject*> exceptionStack, JS::MutableHandle<JSObject*> stackObj, + JS::MutableHandle<JSObject*> stackGlobal); + +// Return a name for the realm. +// This function makes reasonable efforts to make this name both mostly +// human-readable and unique. However, there are no guarantees of either +// property. +extern void GetCurrentRealmName(JSContext*, nsCString& name); + +nsCString GetFunctionName(JSContext* cx, JS::Handle<JSObject*> obj); + +void AddGCCallback(xpcGCCallback cb); +void RemoveGCCallback(xpcGCCallback cb); + +// We need an exact page size only if we run the binary in automation. +#if defined(XP_DARWIN) && defined(__aarch64__) +const size_t kAutomationPageSize = 16384; +#else +const size_t kAutomationPageSize = 4096; +#endif + +struct alignas(kAutomationPageSize) ReadOnlyPage final { + bool mNonLocalConnectionsDisabled = false; + bool mTurnOffAllSecurityPref = false; + + static void Init(); + +#ifdef MOZ_TSAN + // TSan is confused by write access to read-only section. + static ReadOnlyPage sInstance; +#else + static const volatile ReadOnlyPage sInstance; +#endif + + private: + constexpr ReadOnlyPage() = default; + ReadOnlyPage(const ReadOnlyPage&) = delete; + void operator=(const ReadOnlyPage&) = delete; + + static void Write(const volatile bool* aPtr, bool aValue); +}; + +inline bool AreNonLocalConnectionsDisabled() { + return ReadOnlyPage::sInstance.mNonLocalConnectionsDisabled; +} + +inline bool IsInAutomation() { + if (!ReadOnlyPage::sInstance.mTurnOffAllSecurityPref) { + return false; + } + MOZ_RELEASE_ASSERT(AreNonLocalConnectionsDisabled()); + return true; +} + +void InitializeJSContext(); + +/** + * Extract the native nsID object from a JS ID, IfaceID, ClassID, or ContractID + * value. + * + * Returns 'Nothing()' if 'aVal' does is not one of the supported ID types. + */ +mozilla::Maybe<nsID> JSValue2ID(JSContext* aCx, JS::Handle<JS::Value> aVal); + +/** + * Reflect an ID into JS + */ +bool ID2JSValue(JSContext* aCx, const nsID& aId, + JS::MutableHandle<JS::Value> aVal); + +/** + * Reflect an IfaceID into JS + * + * This object will expose constants from the selected interface, and support + * 'instanceof', in addition to the other methods available on JS ID objects. + * + * Use 'xpc::JSValue2ID' to unwrap JS::Values created with this function. + */ +bool IfaceID2JSValue(JSContext* aCx, const nsXPTInterfaceInfo& aInfo, + JS::MutableHandle<JS::Value> aVal); + +/** + * Reflect a ContractID into JS + * + * This object will expose 'getService' and 'createInstance' methods in addition + * to the other methods available on nsID objects. + * + * Use 'xpc::JSValue2ID' to unwrap JS::Values created with this function. + */ +bool ContractID2JSValue(JSContext* aCx, JSString* aContract, + JS::MutableHandle<JS::Value> aVal); + +class JSStackFrameBase { + public: + virtual void Clear() = 0; +}; + +void RegisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame); +void UnregisterJSStackFrame(JS::Realm* aRealm, JSStackFrameBase* aStackFrame); +void NukeJSStackFrames(JS::Realm* aRealm); + +// Check whether the given jsid is a property name (string or symbol) whose +// value can be gotten cross-origin. Cross-origin gets always return undefined +// as the value, unless the Xray actually provides a different value. +bool IsCrossOriginWhitelistedProp(JSContext* cx, + JS::Handle<JS::PropertyKey> id); + +// Appends to props the jsids for property names (strings or symbols) whose +// value can be gotten cross-origin. +bool AppendCrossOriginWhitelistedPropNames( + JSContext* cx, JS::MutableHandle<JS::StackGCVector<JS::PropertyKey>> props); +} // namespace xpc + +namespace mozilla { +namespace dom { + +/** + * This is used to prevent UA widget code from directly creating and adopting + * nodes via the content document, since they should use the special + * create-and-insert apis instead. + */ +bool IsNotUAWidget(JSContext* cx, JSObject* /* unused */); + +/** + * A test for whether WebIDL methods that should only be visible to + * chrome, XBL scopes, or UA Widget scopes. + */ +bool IsChromeOrUAWidget(JSContext* cx, JSObject* /* unused */); + +/** + * Same as IsChromeOrUAWidget but can be used in worker threads as well. + */ +bool ThreadSafeIsChromeOrUAWidget(JSContext* cx, JSObject* obj); + +} // namespace dom + +/** + * Fill the given vector with the buildid. + */ +bool GetBuildId(JS::BuildIdCharVector* aBuildID); + +} // namespace mozilla + +#endif diff --git a/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.cpp b/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.cpp new file mode 100644 index 0000000000..95982733cd --- /dev/null +++ b/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcrtfuzzing/xpcrtfuzzing.h" + +#include "mozilla/Assertions.h" // MOZ_CRASH +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <stdio.h> // fflush, fprintf, fputs + +#include "FuzzingInterface.h" +#include "jsapi.h" + +#include "js/CompilationAndEvaluation.h" // JS::Evaluate +#include "js/CompileOptions.h" // JS::CompileOptions +#include "js/Conversions.h" // JS::Conversions +#include "js/ErrorReport.h" // JS::PrintError +#include "js/Exception.h" // JS::StealPendingExceptionStack +#include "js/experimental/TypedData.h" // JS_GetUint8ClampedArrayData, JS_NewUint8ClampedArray +#include "js/PropertyAndElement.h" // JS_SetProperty, JS_HasOwnProperty +#include "js/RootingAPI.h" // JS::Rooted +#include "js/SourceText.h" // JS::Source{Ownership,Text} +#include "js/Value.h" // JS::Value + +using mozilla::dom::AutoJSAPI; + +static AutoJSAPI* gJsapi = nullptr; +static std::string gFuzzModuleName; + +static void CrashOnPendingException() { + if (gJsapi->HasException()) { + gJsapi->ReportException(); + + MOZ_CRASH("Unhandled exception from JS runtime!"); + } +} + +int FuzzXPCRuntimeStart(AutoJSAPI* jsapi, int* argc, char*** argv, + LibFuzzerDriver fuzzerDriver) { + gFuzzModuleName = getenv("FUZZER"); + gJsapi = jsapi; + + int ret = FuzzXPCRuntimeInit(); + if (ret) { + fprintf(stderr, "Fuzzing Interface: Error: Initialize callback failed\n"); + return ret; + } + + ret = fuzzerDriver(argc, argv, FuzzXPCRuntimeFuzz); + if (!ret) { + fprintf(stdout, "Trying to shutdown!\n"); + int shutdown = FuzzXPCRuntimeShutdown(); + if (shutdown) { + fprintf(stderr, "Fuzzing Interface: Error: Shutdown callback failed\n"); + return shutdown; + } + } + + return ret; +} + +int FuzzXPCRuntimeInit() { + JSContext* cx = gJsapi->cx(); + JS::Rooted<JS::Value> v(cx); + JS::CompileOptions opts(cx); + + // Load the fuzzing module specified in the FUZZER environment variable + JS::EvaluateUtf8Path(cx, opts, gFuzzModuleName.c_str(), &v); + + // Any errors while loading the fuzzing module should be fatal + CrashOnPendingException(); + + return 0; +} + +int FuzzXPCRuntimeFuzz(const uint8_t* buf, size_t size) { + if (!size) { + return 0; + } + + JSContext* cx = gJsapi->cx(); + + JS::Rooted<JSObject*> arr(cx, JS_NewUint8ClampedArray(cx, size)); + if (!arr) { + MOZ_CRASH("OOM"); + } + + do { + JS::AutoCheckCannotGC nogc; + bool isShared; + uint8_t* data = JS_GetUint8ClampedArrayData(arr, &isShared, nogc); + MOZ_RELEASE_ASSERT(!isShared); + memcpy(data, buf, size); + } while (false); + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + JS::RootedValue arrVal(cx, JS::ObjectValue(*arr)); + if (!JS_SetProperty(cx, global, "fuzzBuf", arrVal)) { + MOZ_CRASH("JS_SetProperty failed"); + } + + JS::Rooted<JS::Value> v(cx); + JS::CompileOptions opts(cx); + + static const char data[] = "JSFuzzIterate();"; + + JS::SourceText<mozilla::Utf8Unit> srcBuf; + if (!srcBuf.init(cx, data, strlen(data), JS::SourceOwnership::Borrowed)) { + return 1; + } + + if (!JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &v) && + !JS_IsExceptionPending(cx)) { + // A return value of `false` without a pending exception indicates + // a timeout as triggered by the `timeout` shell function. + return 1; + } + + // The fuzzing module is required to handle any exceptions + CrashOnPendingException(); + + int32_t ret = 0; + if (!ToInt32(cx, v, &ret)) { + MOZ_CRASH("Must return an int32 compatible return value!"); + } + + return ret; +} + +int FuzzXPCRuntimeShutdown() { + JSContext* cx = gJsapi->cx(); + JS::Rooted<JS::Value> v(cx); + JS::CompileOptions opts(cx); + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + + bool found = false; + if (JS_HasOwnProperty(cx, global, "JSFuzzShutdown", &found)) { + if (found) { + static const char data[] = "JSFuzzShutdown();"; + JS::SourceText<mozilla::Utf8Unit> srcBuf; + if (!srcBuf.init(cx, data, strlen(data), JS::SourceOwnership::Borrowed)) { + return 1; + } + + if (!JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, + &v) && + !JS_IsExceptionPending(cx)) { + // A return value of `false` without a pending exception indicates + // a timeout as triggered by the `timeout` shell function. + return 1; + } + } + } + + // The fuzzing module is required to handle any exceptions + CrashOnPendingException(); + + return 0; +} diff --git a/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.h b/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.h new file mode 100644 index 0000000000..89cdf5996b --- /dev/null +++ b/js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.h @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +// xpcrtfuzzing.h - Functionality for XPC runtime fuzzing + +#ifndef shell_xpcrtfuzzing_h +#define shell_xpcrtfuzzing_h + +#include "mozilla/dom/ScriptSettings.h" // mozilla::dom::AutoJSAPI +#include "FuzzerRegistry.h" // LibFuzzerDriver + +// This is the entry point of the XPC runtime fuzzing code from the XPC shell +int FuzzXPCRuntimeStart(mozilla::dom::AutoJSAPI* jsapi, int* argc, char*** argv, + LibFuzzerDriver); + +// These are the traditional libFuzzer-style functions for initialization +// and fuzzing iteration. +int FuzzXPCRuntimeInit(); +int FuzzXPCRuntimeFuzz(const uint8_t* buf, size_t size); +int FuzzXPCRuntimeShutdown(); + +#endif /* shell_xpcrtfuzzing_h */ |