summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/src
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/src')
-rw-r--r--js/xpconnect/src/BackstagePass.h94
-rw-r--r--js/xpconnect/src/ExportHelpers.cpp599
-rw-r--r--js/xpconnect/src/JSServices.cpp172
-rw-r--r--js/xpconnect/src/JSServices.h18
-rw-r--r--js/xpconnect/src/README3
-rw-r--r--js/xpconnect/src/Sandbox.cpp2258
-rw-r--r--js/xpconnect/src/SandboxPrivate.h128
-rw-r--r--js/xpconnect/src/XPCCallContext.cpp218
-rw-r--r--js/xpconnect/src/XPCComponents.cpp2637
-rw-r--r--js/xpconnect/src/XPCConvert.cpp1668
-rw-r--r--js/xpconnect/src/XPCDebug.cpp58
-rw-r--r--js/xpconnect/src/XPCException.cpp77
-rw-r--r--js/xpconnect/src/XPCForwards.h51
-rw-r--r--js/xpconnect/src/XPCInlines.h367
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp1451
-rw-r--r--js/xpconnect/src/XPCJSID.cpp626
-rw-r--r--js/xpconnect/src/XPCJSMemoryReporter.h31
-rw-r--r--js/xpconnect/src/XPCJSRuntime.cpp3217
-rw-r--r--js/xpconnect/src/XPCJSWeakReference.cpp89
-rw-r--r--js/xpconnect/src/XPCJSWeakReference.h28
-rw-r--r--js/xpconnect/src/XPCLocale.cpp140
-rw-r--r--js/xpconnect/src/XPCLog.cpp84
-rw-r--r--js/xpconnect/src/XPCLog.h69
-rw-r--r--js/xpconnect/src/XPCMaps.cpp191
-rw-r--r--js/xpconnect/src/XPCMaps.h386
-rw-r--r--js/xpconnect/src/XPCModule.cpp19
-rw-r--r--js/xpconnect/src/XPCModule.h25
-rw-r--r--js/xpconnect/src/XPCRuntimeService.cpp215
-rw-r--r--js/xpconnect/src/XPCSelfHostedShmem.cpp116
-rw-r--r--js/xpconnect/src/XPCSelfHostedShmem.h89
-rw-r--r--js/xpconnect/src/XPCShellImpl.cpp1517
-rw-r--r--js/xpconnect/src/XPCString.cpp283
-rw-r--r--js/xpconnect/src/XPCThrower.cpp188
-rw-r--r--js/xpconnect/src/XPCVariant.cpp764
-rw-r--r--js/xpconnect/src/XPCWrappedJS.cpp686
-rw-r--r--js/xpconnect/src/XPCWrappedJSClass.cpp1094
-rw-r--r--js/xpconnect/src/XPCWrappedJSIterator.cpp92
-rw-r--r--js/xpconnect/src/XPCWrappedNative.cpp1833
-rw-r--r--js/xpconnect/src/XPCWrappedNativeInfo.cpp728
-rw-r--r--js/xpconnect/src/XPCWrappedNativeJSOps.cpp1236
-rw-r--r--js/xpconnect/src/XPCWrappedNativeProto.cpp151
-rw-r--r--js/xpconnect/src/XPCWrappedNativeScope.cpp497
-rw-r--r--js/xpconnect/src/XPCWrapper.cpp90
-rw-r--r--js/xpconnect/src/XPCWrapper.h29
-rw-r--r--js/xpconnect/src/components.conf16
-rw-r--r--js/xpconnect/src/jsshell.msg12
-rw-r--r--js/xpconnect/src/moz.build79
-rw-r--r--js/xpconnect/src/nsIXPConnect.h291
-rw-r--r--js/xpconnect/src/nsXPConnect.cpp1171
-rw-r--r--js/xpconnect/src/xpc.msg261
-rw-r--r--js/xpconnect/src/xpcObjectHelper.h68
-rw-r--r--js/xpconnect/src/xpcprivate.h2829
-rw-r--r--js/xpconnect/src/xpcpublic.h1023
-rw-r--r--js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.cpp162
-rw-r--r--js/xpconnect/src/xpcrtfuzzing/xpcrtfuzzing.h25
55 files changed, 30249 insertions, 0 deletions
diff --git a/js/xpconnect/src/BackstagePass.h b/js/xpconnect/src/BackstagePass.h
new file mode 100644
index 0000000000..1e6c5531eb
--- /dev/null
+++ b/js/xpconnect/src/BackstagePass.h
@@ -0,0 +1,94 @@
+/* -*- 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/SchedulerGroup.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;
+ }
+
+ nsISerialEventTarget* SerialEventTarget() const final {
+ return mozilla::GetMainThreadSerialEventTarget();
+ }
+ nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const final {
+ return mozilla::SchedulerGroup::Dispatch(std::move(aRunnable));
+ }
+
+ bool ShouldResistFingerprinting(RFPTarget aTarget) 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..a59e9087ec
--- /dev/null
+++ b/js/xpconnect/src/ExportHelpers.cpp
@@ -0,0 +1,599 @@
+/* -*- 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 "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);
+ JS::Rooted<JSFunction*> fun(cx, JS_GetObjectFunction(funObj));
+ if (fun) {
+ if (!JS_GetFunctionId(cx, fun, &funName)) {
+ return false;
+ }
+ }
+ 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..77dbf2d02e
--- /dev/null
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -0,0 +1,2258 @@
+/* -*- 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 "nsGlobalWindowInner.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));
+ JS::Rooted<JSFunction*> fun(cx, JS_ValueToFunction(cx, funval));
+ if (!fun) {
+ XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx);
+ return false;
+ }
+
+ // Use the actual function name as the name.
+ if (!JS_GetFunctionId(cx, fun, &funname)) {
+ return false;
+ }
+ 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..407a6beb8c
--- /dev/null
+++ b/js/xpconnect/src/SandboxPrivate.h
@@ -0,0 +1,128 @@
+/* -*- 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/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "nsContentUtils.h"
+#include "nsIGlobalObject.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIPrincipal.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 final : 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);
+
+ JS::SetRealmReduceTimerPrecisionCallerType(
+ js::GetNonCCWObjectRealm(global),
+ RTPCallerTypeToToken(GetPrivate(global)->GetRTPCallerType()));
+ }
+
+ 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); }
+
+ nsISerialEventTarget* SerialEventTarget() const final {
+ return mozilla::GetMainThreadSerialEventTarget();
+ }
+ nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const final {
+ return mozilla::SchedulerGroup::Dispatch(std::move(aRunnable));
+ }
+
+ 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) 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..9fa4e629aa
--- /dev/null
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -0,0 +1,2637 @@
+/* -*- 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 "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/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/Try.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 "nsGlobalWindowInner.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::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.c_str()), fileUni);
+
+ 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, err->column.oneOriginValue(), 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);
+}
+
+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..560ac375d7
--- /dev/null
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -0,0 +1,1668 @@
+/* -*- 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 Latin1 string in JSAPI.
+ nsStringBuffer* buf;
+ if (!XPCStringConvert::Latin1ToJSVal(cx, *cString, &buf, d)) {
+ return false;
+ }
+ if (buf) {
+ buf->AddRef();
+ }
+ 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;
+ size_t length;
+ if (s.isString()) {
+ str = s.toString();
+
+ length = JS::GetStringLength(str);
+ if (!length) {
+ rs->Truncate();
+ return true;
+ }
+
+ // The string can be an external latin-1 string created in
+ // XPCConvert::NativeData2JS's nsXPTType::T_CSTRING case.
+ if (XPCStringConvert::MaybeAssignLatin1StringChars(str, length, *rs)) {
+ return true;
+ }
+ } else {
+ str = ToString(cx, s);
+ if (!str) {
+ return false;
+ }
+
+ 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.c_str()),
+ linebuf ? nsDependentString(linebuf, report->linebufLength())
+ : EmptyString(),
+ report->lineno, report->column.oneOriginValue(), 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, &current, 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, &current) ||
+ !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..7bf574f675
--- /dev/null
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -0,0 +1,1451 @@
+/* -*- 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/Prefs.h"
+#include "js/WasmFeatures.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WakeLockBinding.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 "nsGlobalWindowInner.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();
+ }
+ if (nsCOMPtr<Document> doc = win->GetExtantDoc()) {
+ doc->UnlockAllWakeLocks(WakeLockType::Screen);
+ }
+ 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);
+
+void xpc::SetPrefableRealmOptions(JS::RealmOptions& options) {
+ options.creationOptions()
+ .setSharedMemoryAndAtomicsEnabled(sSharedMemoryEnabled)
+ .setCoopAndCoepEnabled(
+ StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy() &&
+ StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy());
+}
+
+void xpc::SetPrefableCompileOptions(JS::PrefableCompileOptions& options) {
+ options
+ .setSourcePragmas(StaticPrefs::javascript_options_source_pragmas())
+#ifdef NIGHTLY_BUILD
+ .setImportAttributes(
+ StaticPrefs::javascript_options_experimental_import_attributes())
+ .setImportAttributesAssertSyntax(
+ StaticPrefs::
+ javascript_options_experimental_import_attributes_assert_syntax())
+#endif
+ .setAsmJS(StaticPrefs::javascript_options_asmjs())
+ .setThrowOnAsmJSValidationFailure(
+ StaticPrefs::javascript_options_throw_on_asmjs_validation_failure());
+}
+
+void xpc::SetPrefableContextOptions(JS::ContextOptions& options) {
+ options
+#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, STAGE, COMPILE_PRED, COMPILER_PRED, \
+ FLAG_PRED, FLAG_FORCE_ON, FLAG_FUZZ_ON, SHELL, PREF) \
+ .setWasm##NAME(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_" PREF))
+ JS_FOR_WASM_FEATURES(WASM_FEATURE)
+#undef WASM_FEATURE
+ .setWasmVerbose(Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_verbose"))
+ .setAsyncStack(Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack"))
+ .setAsyncStackCaptureDebuggeeOnly(Preferences::GetBool(
+ JS_OPTIONS_DOT_STR "asyncstack_capture_debuggee_only"));
+
+ SetPrefableCompileOptions(options.compileOptions());
+}
+
+// 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.
+
+ // Note: JS::Prefs are set earlier in startup, in InitializeJS in
+ // XPCOMInit.cpp.
+
+ 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
+
+ bool writeProtectCode = true;
+ if (XRE_IsContentProcess()) {
+ writeProtectCode =
+ StaticPrefs::javascript_options_content_process_write_protect_code();
+ }
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_WRITE_PROTECT_CODE,
+ writeProtectCode);
+
+ if (disableWasmHugeMemory) {
+ bool disabledHugeMemory = JS::DisableWasmHugeMemory();
+ MOZ_RELEASE_ASSERT(disabledHugeMemory);
+ }
+}
+
+static void ReloadPrefsCallback(const char* pref, void* aXpccx) {
+ // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above.
+
+ // Update all non-startup JS::Prefs.
+ SET_NON_STARTUP_JS_PREFS_FROM_BROWSER_PREFS;
+
+ 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");
+
+#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"));
+
+ 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, StaticPrefs::javascript_options_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:
+ TaskResult Run() override {
+ JS::RunHelperThreadTask();
+ return TaskResult::Complete;
+ }
+ explicit HelperThreadTaskHandler()
+ : Task(Kind::OffMainThreadOnly, 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(), &registered);
+ 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..c4d272b950
--- /dev/null
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -0,0 +1,3217 @@
+/* -*- 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 "js/Utility.h" // JS::UniqueTwoByteChars
+#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 "nsGlobalWindowInner.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(), 1000,
+ 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);
+}
+
+JSObject* SandboxPrototypeOrNull(JSContext* aCx, JSObject* aObj) {
+ 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;
+ }
+
+ return js::CheckedUnwrapDynamic(proto, aCx, /* stopAtWindowProxy = */ false);
+}
+
+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 + "cacheir-stubs"_ns, zStats.cacheIRStubs,
+ "The JIT's 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 + "alloc-sites"_ns, realmStats.allocSites,
+ "GC allocation site data associated with IC stubs.");
+
+ 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.");
+
+ 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/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
+
+ switch (id) {
+ case JSMetric::DESERIALIZE_BYTES:
+ glean::performance_clone_deserialize::size.Accumulate(sample);
+ break;
+ case JSMetric::DESERIALIZE_ITEMS:
+ glean::performance_clone_deserialize::items.AccumulateSamples({sample});
+ break;
+ case JSMetric::DESERIALIZE_US:
+ glean::performance_clone_deserialize::time.AccumulateRawDuration(
+ TimeDuration::FromMicroseconds(sample));
+ break;
+ case JSMetric::GC_MS:
+ glean::javascript_gc::total_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ case JSMetric::GC_MINOR_US:
+ glean::javascript_gc::minor_time.AccumulateRawDuration(
+ TimeDuration::FromMicroseconds(sample));
+ break;
+ case JSMetric::GC_PREPARE_MS:
+ glean::javascript_gc::prepare_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ case JSMetric::GC_MARK_ROOTS_US:
+ glean::javascript_gc::mark_roots_time.AccumulateRawDuration(
+ TimeDuration::FromMicroseconds(sample));
+ break;
+ case JSMetric::GC_MARK_MS:
+ glean::javascript_gc::mark_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ case JSMetric::GC_SWEEP_MS:
+ glean::javascript_gc::sweep_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ case JSMetric::GC_COMPACT_MS:
+ glean::javascript_gc::compact_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ case JSMetric::GC_SLICE_MS:
+ glean::javascript_gc::slice_time.AccumulateRawDuration(
+ TimeDuration::FromMilliseconds(sample));
+ break;
+ default:
+ // The rest aren't relayed to Glean.
+ break;
+ }
+}
+
+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;
+ case JSUseCounter::WASM_LEGACY_EXCEPTIONS:
+ SetUseCounter(obj, eUseCounter_custom_JS_wasm_legacy_exceptions);
+ break;
+ case JSUseCounter::LATE_WEEKDAY:
+ SetUseCounter(obj, eUseCounter_custom_JS_late_weekday);
+ 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 |chars| and |*len|.
+ JS::UniqueTwoByteChars chars;
+ rv = ScriptLoader::ConvertToUTF16(
+ scriptChannel, reinterpret_cast<const unsigned char*>(buf.get()),
+ rawLen, u"UTF-8"_ns, nullptr, chars, *len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!chars) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *twoByteSource = chars.release();
+ }
+
+ 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>()),
+ mGCIsRunning(false),
+ 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..257679ab08
--- /dev/null
+++ b/js/xpconnect/src/XPCLocale.cpp
@@ -0,0 +1,140 @@
+/* -*- 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);
+}
+
+NS_IMETHODIMP
+XPCLocaleObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "intl:app-locales-changed")) {
+ 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 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..38a02e9b2f
--- /dev/null
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -0,0 +1,1517 @@
+/* -*- 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);
+
+ RootedString str(cx);
+ JS::UniqueChars opt;
+ if (args.length() > 0) {
+ str = ToString(cx, args[0]);
+ if (!str) {
+ return false;
+ }
+
+ opt = JS_EncodeStringToUTF8(cx, str);
+ if (!opt) {
+ return false;
+ }
+
+ JS_ReportErrorUTF8(cx, "unknown option name '%s'.", opt.get());
+ return false;
+ }
+
+ args.rval().setString(JS_GetEmptyString(cx));
+ 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::AutoIOInterposer ioInterposerGuard;
+ ioInterposerGuard.Init();
+
+ 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..5d784a02fd
--- /dev/null
+++ b/js/xpconnect/src/XPCString.cpp
@@ -0,0 +1,283 @@
+/* -*- 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;
+
+void XPCStringConvert::LiteralExternalString::finalize(
+ JS::Latin1Char* aChars) const {
+ // Nothing to do.
+}
+
+void XPCStringConvert::LiteralExternalString::finalize(char16_t* aChars) const {
+ // Nothing to do.
+}
+
+size_t XPCStringConvert::LiteralExternalString::sizeOfBuffer(
+ const JS::Latin1Char* aChars, mozilla::MallocSizeOf aMallocSizeOf) const {
+ // This string's buffer is not heap-allocated, so its malloc size is 0.
+ return 0;
+}
+
+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(
+ JS::Latin1Char* aChars) const {
+ nsStringBuffer* buf = nsStringBuffer::FromData(aChars);
+ buf->Release();
+}
+
+void XPCStringConvert::DOMStringExternalString::finalize(
+ char16_t* aChars) const {
+ nsStringBuffer* buf = nsStringBuffer::FromData(aChars);
+ buf->Release();
+}
+
+size_t XPCStringConvert::DOMStringExternalString::sizeOfBuffer(
+ const JS::Latin1Char* 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<JS::Latin1Char*>(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);
+}
+
+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);
+}
+
+// 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 (!UCStringBufferToJSVal(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;
+}
+
+bool XPCStringConvert::Latin1ToJSVal(JSContext* cx, const nsACString& latin1,
+ nsStringBuffer** sharedBuffer,
+ MutableHandleValue vp) {
+ *sharedBuffer = nullptr;
+
+ uint32_t length = latin1.Length();
+
+ if (latin1.IsLiteral()) {
+ return StringLiteralToJSVal(
+ cx, reinterpret_cast<const JS::Latin1Char*>(latin1.BeginReading()),
+ length, vp);
+ }
+
+ nsStringBuffer* buf = nsStringBuffer::FromString(latin1);
+ if (buf) {
+ bool shared;
+ if (!Latin1StringBufferToJSVal(cx, buf, length, vp, &shared)) {
+ return false;
+ }
+ if (shared) {
+ *sharedBuffer = buf;
+ }
+ return true;
+ }
+
+ JSString* str = JS_NewStringCopyN(cx, latin1.BeginReading(), length);
+ if (!str) {
+ return false;
+ }
+ vp.setString(str);
+ return true;
+}
+
+bool XPCStringConvert::UTF8ToJSVal(JSContext* cx, const nsACString& utf8,
+ nsStringBuffer** sharedBuffer,
+ MutableHandleValue vp) {
+ *sharedBuffer = nullptr;
+
+ uint32_t length = utf8.Length();
+
+ if (utf8.IsLiteral()) {
+ return UTF8StringLiteralToJSVal(
+ cx, JS::UTF8Chars(utf8.BeginReading(), length), vp);
+ }
+
+ nsStringBuffer* buf = nsStringBuffer::FromString(utf8);
+ if (buf) {
+ bool shared;
+ if (!UTF8StringBufferToJSVal(cx, buf, length, vp, &shared)) {
+ return false;
+ }
+ if (shared) {
+ *sharedBuffer = buf;
+ }
+ return true;
+ }
+
+ JSString* str =
+ JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(utf8.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;
+}
+
+bool NonVoidStringToJsval(JSContext* cx, const 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.
+ sharedBuffer->AddRef();
+ }
+ return true;
+}
+
+bool NonVoidLatin1StringToJsval(JSContext* cx, nsACString& str,
+ MutableHandleValue rval) {
+ nsStringBuffer* sharedBuffer;
+ if (!XPCStringConvert::Latin1ToJSVal(cx, str, &sharedBuffer, rval)) {
+ return false;
+ }
+
+ if (sharedBuffer) {
+ // The string was shared but Latin1ToJSVal didn't addref it.
+ // Move the ownership from str to jsstr.
+ str.ForgetSharedBuffer();
+ }
+ return true;
+}
+
+bool NonVoidLatin1StringToJsval(JSContext* cx, const nsACString& str,
+ MutableHandleValue rval) {
+ nsStringBuffer* sharedBuffer;
+ if (!XPCStringConvert::Latin1ToJSVal(cx, str, &sharedBuffer, rval)) {
+ return false;
+ }
+
+ if (sharedBuffer) {
+ // The string was shared but Latin1ToJSVal didn't addref it.
+ sharedBuffer->AddRef();
+ }
+ return true;
+}
+
+bool NonVoidUTF8StringToJsval(JSContext* cx, nsACString& str,
+ MutableHandleValue rval) {
+ nsStringBuffer* sharedBuffer;
+ if (!XPCStringConvert::UTF8ToJSVal(cx, str, &sharedBuffer, rval)) {
+ return false;
+ }
+
+ if (sharedBuffer) {
+ // The string was shared but UTF8ToJSVal didn't addref it.
+ // Move the ownership from str to jsstr.
+ str.ForgetSharedBuffer();
+ }
+ return true;
+}
+
+bool NonVoidUTF8StringToJsval(JSContext* cx, const nsACString& str,
+ MutableHandleValue rval) {
+ nsStringBuffer* sharedBuffer;
+ if (!XPCStringConvert::UTF8ToJSVal(cx, str, &sharedBuffer, rval)) {
+ return false;
+ }
+
+ if (sharedBuffer) {
+ // The string was shared but UTF8ToJSVal didn't addref it.
+ sharedBuffer->AddRef();
+ }
+ 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, &param_iid) ||
+ !GetArraySizeFromParam(info, type, nativeParams, &array_count))
+ goto pre_call_clean_up;
+
+ if (!XPCConvert::NativeData2JS(cx, &val, pv, type, &param_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 (&param == 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,
+ &param_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 (&param == 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, &param_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,
+ &param_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..0e980311ff
--- /dev/null
+++ b/js/xpconnect/src/XPCWrappedJSIterator.cpp
@@ -0,0 +1,92 @@
+/* -*- 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/Try.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..ee0afb604b
--- /dev/null
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -0,0 +1,1833 @@
+/* -*- 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,
+ 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);
+
+ // 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, &param.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 = &param->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 = &paramInfo == 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, &param_iid) ||
+ !GetArraySizeFromParam(type, UndefinedHandleValue, &array_count))
+ return false;
+
+ nsresult err;
+ if (!XPCConvert::NativeData2JS(mCallContext, &v, &dp->val, type, &param_iid,
+ array_count, &err)) {
+ ThrowBadParam(err, i, mCallContext);
+ return false;
+ }
+
+ if (&paramInfo == 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, &param_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, &param_iid) ||
+ !GetArraySizeFromParam(type, src, &array_count))
+ return false;
+
+ nsresult err;
+
+ if (!XPCConvert::JSData2Native(mCallContext, &dp->val, src, type, &param_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, &param.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..aeb43e062c
--- /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 = 1;
+ 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..5b98e8cad5
--- /dev/null
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -0,0 +1,1171 @@
+/* -*- 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 "nsRFPService.h"
+#include "prsystem.h"
+
+#include "xpcprivate.h"
+
+#ifdef XP_WIN
+# include "mozilla/WinHeaderOnlyUtils.h"
+#else
+# include <sys/mman.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace xpc;
+using namespace JS;
+
+NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect)
+
+nsXPConnect* nsXPConnect::gSelf = nullptr;
+bool nsXPConnect::gOnceAliveNowDead = false;
+
+// Global cache of the default script security manager (QI'd to
+// nsIScriptSecurityManager) and the system principal.
+nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr;
+nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr;
+
+const char XPC_EXCEPTION_CONTRACTID[] = "@mozilla.org/js/xpc/Exception;1";
+const char XPC_CONSOLE_CONTRACTID[] = "@mozilla.org/consoleservice;1";
+const char XPC_SCRIPT_ERROR_CONTRACTID[] = "@mozilla.org/scripterror;1";
+
+/***************************************************************************/
+
+nsXPConnect::nsXPConnect() {
+#ifdef MOZ_GECKO_PROFILER
+ JS::SetProfilingThreadCallbacks(profiler_register_thread,
+ profiler_unregister_thread);
+#endif
+}
+
+// static
+void nsXPConnect::InitJSContext() {
+ MOZ_ASSERT(!gSelf->mContext);
+
+ XPCJSContext* xpccx = XPCJSContext::NewXPCJSContext();
+ if (!xpccx) {
+ MOZ_CRASH("Couldn't create XPCJSContext.");
+ }
+ gSelf->mContext = xpccx;
+ gSelf->mRuntime = xpccx->Runtime();
+
+ mozJSModuleLoader::InitStatics();
+
+ // Initialize the script preloader cache.
+ Unused << mozilla::ScriptPreloader::GetSingleton();
+
+ nsJSContext::EnsureStatics();
+}
+
+void xpc::InitializeJSContext() { nsXPConnect::InitJSContext(); }
+
+nsXPConnect::~nsXPConnect() {
+ MOZ_ASSERT(mRuntime);
+
+ mRuntime->DeleteSingletonScopes();
+
+ // In order to clean up everything properly, we need to GC twice: once now,
+ // to clean anything that can go away on its own (like the Junk Scope, which
+ // we unrooted above), and once after forcing a bunch of shutdown in
+ // XPConnect, to clean the stuff we forcibly disconnected. The forced
+ // shutdown code defaults to leaking in a number of situations, so we can't
+ // get by with only the second GC. :-(
+ //
+ // 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
+
+ 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.c_str()),
+ mFileName);
+ }
+
+ mSourceId = aReport->sourceId;
+ mLineNumber = aReport->lineno;
+ mColumn = aReport->column.oneOriginValue();
+}
+
+void xpc::ErrorNote::Init(JSErrorNotes::Note* aNote) {
+ xpc::ErrorBase::Init(aNote);
+
+ ErrorNoteToMessageString(aNote, mErrorMsg);
+}
+
+void xpc::ErrorReport::Init(JSErrorReport* aReport, const char* aToStringResult,
+ bool aIsChrome, uint64_t aWindowID) {
+ xpc::ErrorBase::Init(aReport);
+ mCategory = aIsChrome ? "chrome javascript"_ns : "content javascript"_ns;
+ mWindowID = aWindowID;
+
+ if (aToStringResult) {
+ AppendUTF8toUTF16(mozilla::MakeStringSpan(aToStringResult), mErrorMsg);
+ }
+ if (mErrorMsg.IsEmpty()) {
+ ErrorReportToMessageString(aReport, mErrorMsg);
+ }
+ if (mErrorMsg.IsEmpty()) {
+ mErrorMsg.AssignLiteral("<unknown>");
+ }
+
+ 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 aSecureContext,
+ bool aForceUTC, bool aAlwaysUseFdlibm,
+ bool aLocaleEnUS) {
+ if (aIsSystemPrincipal) {
+ // Make toSource functions [ChromeOnly]
+ aOptions.creationOptions().setToSourceEnabled(true);
+ // Make sure [SecureContext] APIs are visible:
+ aOptions.creationOptions().setSecureContext(true);
+ aOptions.behaviors().setClampAndJitterTime(false);
+ aOptions.behaviors().setDiscardSource(ShouldDiscardSystemSource());
+ MOZ_ASSERT(aSecureContext,
+ "aIsSystemPrincipal should imply aSecureContext");
+ } else {
+ aOptions.creationOptions().setSecureContext(aSecureContext);
+ }
+
+ aOptions.creationOptions().setForceUTC(aForceUTC);
+ aOptions.creationOptions().setAlwaysUseFdlibm(aAlwaysUseFdlibm);
+ if (aLocaleEnUS) {
+ nsCString locale = nsRFPService::GetSpoofedJSLocale();
+ aOptions.creationOptions().setLocaleCopyZ(locale.get());
+ }
+}
+
+bool InitGlobalObject(JSContext* aJSContext, JS::Handle<JSObject*> aGlobal,
+ uint32_t aFlags) {
+ // Immediately enter the global's realm so that everything we create
+ // ends up there.
+ JSAutoRealm ar(aJSContext, aGlobal);
+
+ // Stuff coming through this path always ends up as a DOM global.
+ MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL);
+
+ if (!(aFlags & xpc::OMIT_COMPONENTS_OBJECT)) {
+ // XPCCallContext gives us an active request needed to save/restore.
+ if (!ObjectScope(aGlobal)->AttachComponentsObject(aJSContext) ||
+ !XPCNativeWrapper::AttachNewConstructorObject(aJSContext, aGlobal)) {
+ return UnexpectedFailure(false);
+ }
+
+ if (!mozJSModuleLoader::Get()->DefineJSServices(aJSContext, aGlobal)) {
+ return UnexpectedFailure(false);
+ }
+ }
+
+ if (!(aFlags & xpc::DONT_FIRE_ONNEWGLOBALHOOK)) {
+ JS_FireOnNewGlobalObject(aJSContext, aGlobal);
+ }
+
+ return true;
+}
+
+nsresult InitClassesWithNewWrappedGlobal(JSContext* aJSContext,
+ nsISupports* aCOMObj,
+ nsIPrincipal* aPrincipal,
+ uint32_t aFlags,
+ JS::RealmOptions& aOptions,
+ MutableHandleObject aNewGlobal) {
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aCOMObj, "bad param");
+
+ // We pass null for the 'extra' pointer during global object creation, so
+ // we need to have a principal.
+ MOZ_ASSERT(aPrincipal);
+ // All uses (at time of writing) were System Principal, meaning
+ // aShouldResistFingerprinting can be hardcoded to false.
+ // If this changes, ShouldRFP needs to be updated accordingly.
+ MOZ_RELEASE_ASSERT(aPrincipal->IsSystemPrincipal());
+
+ // Similarly we can thus hardcode the RTPCallerType.
+ aOptions.behaviors().setReduceTimerPrecisionCallerType(
+ RTPCallerTypeToToken(RTPCallerType::SystemPrincipal));
+
+ InitGlobalObjectOptions(aOptions, /* aSystemPrincipal */ true,
+ /* aSecureContext */ true,
+ /* aForceUTC */ false, /* aAlwaysUseFdlibm */ false,
+ /* aLocaleEnUS */ false);
+
+ // Call into XPCWrappedNative to make a new global object, scope, and global
+ // prototype.
+ xpcObjectHelper helper(aCOMObj);
+ MOZ_ASSERT(helper.GetScriptableFlags() & XPC_SCRIPTABLE_IS_GLOBAL_OBJECT);
+ RefPtr<XPCWrappedNative> wrappedGlobal;
+ nsresult rv = XPCWrappedNative::WrapNewGlobal(
+ aJSContext, helper, aPrincipal, aOptions, getter_AddRefs(wrappedGlobal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Grab a copy of the global and enter its compartment.
+ RootedObject global(aJSContext, wrappedGlobal->GetFlatJSObject());
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+
+ if (!InitGlobalObject(aJSContext, global, aFlags)) {
+ return UnexpectedFailure(NS_ERROR_FAILURE);
+ }
+
+ { // Scope for JSAutoRealm
+ JSAutoRealm ar(aJSContext, global);
+ if (!JS_DefineProfilingFunctions(aJSContext, global)) {
+ return UnexpectedFailure(NS_ERROR_OUT_OF_MEMORY);
+ }
+ if (aPrincipal->IsSystemPrincipal()) {
+ if (!glean::Glean::DefineGlean(aJSContext, global) ||
+ !glean::GleanPings::DefineGleanPings(aJSContext, global)) {
+ return UnexpectedFailure(NS_ERROR_FAILURE);
+ }
+ }
+ }
+
+ aNewGlobal.set(global);
+ return NS_OK;
+}
+
+nsCString GetFunctionName(JSContext* cx, HandleObject obj) {
+ RootedObject inner(cx, js::UncheckedUnwrap(obj));
+ JSAutoRealm ar(cx, inner);
+
+ RootedFunction fun(cx, JS_GetObjectFunction(inner));
+ if (!fun) {
+ // If the object isn't a function, it's likely that it has a single
+ // function property (for things like nsITimerCallback). In this case,
+ // return the name of that function property.
+
+ Rooted<IdVector> idArray(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, inner, &idArray)) {
+ JS_ClearPendingException(cx);
+ return nsCString("error");
+ }
+
+ if (idArray.length() != 1) {
+ return nsCString("nonfunction");
+ }
+
+ RootedId id(cx, idArray[0]);
+ RootedValue v(cx);
+ if (!JS_GetPropertyById(cx, inner, id, &v)) {
+ JS_ClearPendingException(cx);
+ return nsCString("nonfunction");
+ }
+
+ if (!v.isObject()) {
+ return nsCString("nonfunction");
+ }
+
+ RootedObject vobj(cx, &v.toObject());
+ return GetFunctionName(cx, vobj);
+ }
+
+ RootedString funName(cx, JS_GetMaybePartialFunctionDisplayId(fun));
+ RootedScript script(cx, JS_GetFunctionScript(cx, fun));
+ const char* filename = script ? JS_GetScriptFilename(script) : "anonymous";
+ const char* filenameSuffix = strrchr(filename, '/');
+
+ if (filenameSuffix) {
+ filenameSuffix++;
+ } else {
+ filenameSuffix = filename;
+ }
+
+ nsCString displayName("anonymous");
+ if (funName) {
+ RootedValue funNameVal(cx, StringValue(funName));
+ if (!XPCConvert::JSData2Native(cx, &displayName, funNameVal,
+ {nsXPTType::T_UTF8STRING}, nullptr, 0,
+ nullptr)) {
+ JS_ClearPendingException(cx);
+ return nsCString("anonymous");
+ }
+ }
+
+ displayName.Append('[');
+ displayName.Append(filenameSuffix, strlen(filenameSuffix));
+ displayName.Append(']');
+ return displayName;
+}
+
+} // namespace xpc
+
+static nsresult NativeInterface2JSObject(JSContext* aCx, HandleObject aScope,
+ nsISupports* aCOMObj,
+ nsWrapperCache* aCache,
+ const nsIID* aIID, bool aAllowWrapping,
+ MutableHandleValue aVal) {
+ JSAutoRealm ar(aCx, aScope);
+
+ nsresult rv;
+ xpcObjectHelper helper(aCOMObj, aCache);
+ if (!XPCConvert::NativeInterface2JSObject(aCx, aVal, helper, aIID,
+ aAllowWrapping, &rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(
+ aAllowWrapping || !xpc::WrapperFactory::IsXrayWrapper(&aVal.toObject()),
+ "Shouldn't be returning a xray wrapper here");
+
+ return NS_OK;
+}
+
+nsresult nsIXPConnect::WrapNative(JSContext* aJSContext, JSObject* aScopeArg,
+ nsISupports* aCOMObj, const nsIID& aIID,
+ JSObject** aRetVal) {
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aScopeArg, "bad param");
+ MOZ_ASSERT(aCOMObj, "bad param");
+
+ RootedObject aScope(aJSContext, aScopeArg);
+ RootedValue v(aJSContext);
+ nsresult rv = NativeInterface2JSObject(aJSContext, aScope, aCOMObj, nullptr,
+ &aIID, true, &v);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!v.isObjectOrNull()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aRetVal = v.toObjectOrNull();
+ return NS_OK;
+}
+
+nsresult nsIXPConnect::WrapNativeToJSVal(JSContext* aJSContext,
+ JSObject* aScopeArg,
+ nsISupports* aCOMObj,
+ nsWrapperCache* aCache,
+ const nsIID* aIID, bool aAllowWrapping,
+ MutableHandleValue aVal) {
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aScopeArg, "bad param");
+ MOZ_ASSERT(aCOMObj, "bad param");
+
+ RootedObject aScope(aJSContext, aScopeArg);
+ return NativeInterface2JSObject(aJSContext, aScope, aCOMObj, aCache, aIID,
+ aAllowWrapping, aVal);
+}
+
+nsresult nsIXPConnect::WrapJS(JSContext* aJSContext, JSObject* aJSObjArg,
+ const nsIID& aIID, void** result) {
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aJSObjArg, "bad param");
+ MOZ_ASSERT(result, "bad param");
+
+ *result = nullptr;
+
+ RootedObject aJSObj(aJSContext, aJSObjArg);
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj, &aIID,
+ nullptr, &rv))
+ return rv;
+ return NS_OK;
+}
+
+nsresult nsIXPConnect::JSValToVariant(JSContext* cx, HandleValue aJSVal,
+ nsIVariant** aResult) {
+ MOZ_ASSERT(aResult, "bad param");
+
+ RefPtr<XPCVariant> variant = XPCVariant::newVariant(cx, aJSVal);
+ variant.forget(aResult);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+nsresult nsIXPConnect::WrapJSAggregatedToNative(nsISupports* aOuter,
+ JSContext* aJSContext,
+ JSObject* aJSObjArg,
+ const nsIID& aIID,
+ void** result) {
+ MOZ_ASSERT(aOuter, "bad param");
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aJSObjArg, "bad param");
+ MOZ_ASSERT(result, "bad param");
+
+ *result = nullptr;
+
+ RootedObject aJSObj(aJSContext, aJSObjArg);
+ nsresult rv;
+ if (!XPCConvert::JSObject2NativeInterface(aJSContext, result, aJSObj, &aIID,
+ aOuter, &rv))
+ return rv;
+ return NS_OK;
+}
+
+nsresult nsIXPConnect::GetWrappedNativeOfJSObject(
+ JSContext* aJSContext, JSObject* aJSObjArg,
+ nsIXPConnectWrappedNative** _retval) {
+ MOZ_ASSERT(aJSContext, "bad param");
+ MOZ_ASSERT(aJSObjArg, "bad param");
+ MOZ_ASSERT(_retval, "bad param");
+
+ RootedObject aJSObj(aJSContext, aJSObjArg);
+ aJSObj = js::CheckedUnwrapDynamic(aJSObj, aJSContext,
+ /* stopAtWindowProxy = */ false);
+ if (!aJSObj || !IsWrappedNativeReflector(aJSObj)) {
+ *_retval = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<XPCWrappedNative> temp = XPCWrappedNative::Get(aJSObj);
+ temp.forget(_retval);
+ return NS_OK;
+}
+
+static already_AddRefed<nsISupports> ReflectorToISupports(JSObject* reflector) {
+ if (!reflector) {
+ return nullptr;
+ }
+
+ // Try XPCWrappedNatives.
+ if (IsWrappedNativeReflector(reflector)) {
+ XPCWrappedNative* wn = XPCWrappedNative::Get(reflector);
+ if (!wn) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISupports> native = wn->Native();
+ return native.forget();
+ }
+
+ // Try DOM objects. This QI without taking a ref first is safe, because
+ // this if non-null our thing will definitely be a DOM object, and we know
+ // their QI to nsISupports doesn't do anything weird.
+ nsCOMPtr<nsISupports> canonical =
+ do_QueryInterface(mozilla::dom::UnwrapDOMObjectToISupports(reflector));
+ return canonical.forget();
+}
+
+already_AddRefed<nsISupports> xpc::ReflectorToISupportsStatic(
+ JSObject* reflector) {
+ // Unwrap security wrappers, if allowed.
+ return ReflectorToISupports(js::CheckedUnwrapStatic(reflector));
+}
+
+already_AddRefed<nsISupports> xpc::ReflectorToISupportsDynamic(
+ JSObject* reflector, JSContext* cx) {
+ // Unwrap security wrappers, if allowed.
+ return ReflectorToISupports(
+ js::CheckedUnwrapDynamic(reflector, cx,
+ /* stopAtWindowProxy = */ false));
+}
+
+nsresult nsIXPConnect::CreateSandbox(JSContext* cx, nsIPrincipal* principal,
+ JSObject** _retval) {
+ *_retval = nullptr;
+
+ RootedValue rval(cx);
+ SandboxOptions options;
+ nsresult rv = CreateSandboxObject(cx, &rval, principal, options);
+ MOZ_ASSERT(NS_FAILED(rv) || !rval.isPrimitive(),
+ "Bad return value from xpc_CreateSandboxObject()!");
+
+ if (NS_SUCCEEDED(rv) && !rval.isPrimitive()) {
+ *_retval = rval.toObjectOrNull();
+ }
+
+ return rv;
+}
+
+nsresult nsIXPConnect::EvalInSandboxObject(const nsAString& source,
+ const char* filename, JSContext* cx,
+ JSObject* sandboxArg,
+ MutableHandleValue rval) {
+ if (!sandboxArg) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RootedObject sandbox(cx, sandboxArg);
+ nsCString filenameStr;
+ if (filename) {
+ filenameStr.Assign(filename);
+ } else {
+ 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..48c9215903
--- /dev/null
+++ b/js/xpconnect/src/xpc.msg
@@ -0,0 +1,261 @@
+/* -*- 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")
+XPC_MSG_DEF(NS_ERROR_SUPERFLUOS_AUTH , "User refused navigation to potentially unsafe URL with embedded credentials/superfluos authentication")
+
+/* 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")
+
+XPC_MSG_DEF(NS_ERROR_WDBA_NO_PROGID , "The ProgID classes had not been registered.")
+XPC_MSG_DEF(NS_ERROR_WDBA_HASH_CHECK , "The existing UserChoice Hash could not be verified.")
+XPC_MSG_DEF(NS_ERROR_WDBA_REJECTED , "UserChoice was set, but checking the default did not return our ProgID.")
+XPC_MSG_DEF(NS_ERROR_WDBA_BUILD , "The existing UserChoice Hash was verified, but we're on an older, unsupported Windows build, so do not attempt to update the UserChoice hash.")
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..57a4b1e02e
--- /dev/null
+++ b/js/xpconnect/src/xpcprivate.h
@@ -0,0 +1,2829 @@
+/* -*- 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,
+ 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);
+
+// 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..08da56e2fc
--- /dev/null
+++ b/js/xpconnect/src/xpcpublic.h
@@ -0,0 +1,1023 @@
+/* -*- 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 <type_traits>
+#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/TextUtils.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 PrefableCompileOptions;
+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).
+ * 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 {
+ DONT_FIRE_ONNEWGLOBALHOOK = 1 << 0,
+ OMIT_COMPONENTS_OBJECT = 1 << 1,
+};
+
+} /* 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);
+
+inline void AssignFromStringBuffer(nsStringBuffer* buffer, size_t len,
+ nsAString& dest) {
+ buffer->ToString(len, dest);
+}
+inline void AssignFromStringBuffer(nsStringBuffer* buffer, size_t len,
+ nsACString& dest) {
+ buffer->ToString(len, dest);
+}
+
+// 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);
+ static bool Latin1ToJSVal(JSContext* cx, const nsACString& latin1,
+ nsStringBuffer** sharedBuffer,
+ JS::MutableHandle<JS::Value> vp);
+ static bool UTF8ToJSVal(JSContext* cx, const nsACString& utf8,
+ nsStringBuffer** sharedBuffer,
+ JS::MutableHandle<JS::Value> vp);
+
+ // Convert the given stringbuffer/length pair to a jsval
+ static MOZ_ALWAYS_INLINE bool UCStringBufferToJSVal(
+ JSContext* cx, nsStringBuffer* buf, uint32_t length,
+ JS::MutableHandle<JS::Value> rval, bool* sharedBuffer) {
+ JSString* str = JS_NewMaybeExternalUCString(
+ cx, static_cast<const char16_t*>(buf->Data()), length,
+ &sDOMStringExternalString, sharedBuffer);
+ if (!str) {
+ return false;
+ }
+ rval.setString(str);
+ return true;
+ }
+
+ static MOZ_ALWAYS_INLINE bool Latin1StringBufferToJSVal(
+ JSContext* cx, nsStringBuffer* buf, uint32_t length,
+ JS::MutableHandle<JS::Value> rval, bool* sharedBuffer) {
+ JSString* str = JS_NewMaybeExternalStringLatin1(
+ cx, static_cast<const JS::Latin1Char*>(buf->Data()), length,
+ &sDOMStringExternalString, sharedBuffer);
+ if (!str) {
+ return false;
+ }
+ rval.setString(str);
+ return true;
+ }
+
+ static MOZ_ALWAYS_INLINE bool UTF8StringBufferToJSVal(
+ JSContext* cx, nsStringBuffer* buf, uint32_t length,
+ JS::MutableHandle<JS::Value> rval, bool* sharedBuffer) {
+ JSString* str = JS_NewMaybeExternalStringUTF8(
+ cx, {static_cast<const char*>(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_NewMaybeExternalUCString(
+ cx, literal, length, &sLiteralExternalString, &ignored);
+ if (!str) {
+ return false;
+ }
+ rval.setString(str);
+ return true;
+ }
+
+ static inline bool StringLiteralToJSVal(JSContext* cx,
+ const JS::Latin1Char* literal,
+ uint32_t length,
+ JS::MutableHandle<JS::Value> rval) {
+ bool ignored;
+ JSString* str = JS_NewMaybeExternalStringLatin1(
+ cx, literal, length, &sLiteralExternalString, &ignored);
+ if (!str) {
+ return false;
+ }
+ rval.setString(str);
+ return true;
+ }
+
+ static inline bool UTF8StringLiteralToJSVal(
+ JSContext* cx, const JS::UTF8Chars& chars,
+ JS::MutableHandle<JS::Value> rval) {
+ bool ignored;
+ JSString* str = JS_NewMaybeExternalStringUTF8(
+ cx, chars, &sLiteralExternalString, &ignored);
+ if (!str) {
+ return false;
+ }
+ rval.setString(str);
+ return true;
+ }
+
+ private:
+ static MOZ_ALWAYS_INLINE bool MaybeGetExternalStringChars(
+ JSString* str, const JSExternalStringCallbacks** callbacks,
+ const char16_t** chars) {
+ return JS::IsExternalUCString(str, callbacks, chars);
+ }
+ static MOZ_ALWAYS_INLINE bool MaybeGetExternalStringChars(
+ JSString* str, const JSExternalStringCallbacks** callbacks,
+ const JS::Latin1Char** chars) {
+ return JS::IsExternalStringLatin1(str, callbacks, chars);
+ }
+
+ enum class AcceptedEncoding { All, ASCII };
+
+ template <typename SrcCharT, typename DestCharT, AcceptedEncoding encoding,
+ typename T>
+ static MOZ_ALWAYS_INLINE bool MaybeAssignStringChars(JSString* s, size_t len,
+ T& dest) {
+ MOZ_ASSERT(len == JS::GetStringLength(s));
+ static_assert(sizeof(SrcCharT) == sizeof(DestCharT));
+ if constexpr (encoding == AcceptedEncoding::ASCII) {
+ static_assert(
+ std::is_same_v<DestCharT, char>,
+ "AcceptedEncoding::ASCII can be used only with single byte");
+ }
+
+ const JSExternalStringCallbacks* callbacks;
+ const DestCharT* chars;
+ if (!MaybeGetExternalStringChars(
+ s, &callbacks, reinterpret_cast<const SrcCharT**>(&chars))) {
+ return false;
+ }
+
+ if (callbacks == &sDOMStringExternalString) {
+ if constexpr (encoding == AcceptedEncoding::ASCII) {
+ if (!mozilla::IsAscii(mozilla::Span(chars, len))) {
+ return false;
+ }
+ }
+
+ // The characters represent an existing string buffer that we shared with
+ // JS. We can share that buffer ourselves if the string corresponds to
+ // the whole buffer; otherwise we have to copy.
+ if (chars[len] == '\0') {
+ // NOTE: No need to worry about SrcCharT vs DestCharT, given
+ // nsStringBuffer::FromData takes void*.
+ AssignFromStringBuffer(
+ nsStringBuffer::FromData(const_cast<DestCharT*>(chars)), len, dest);
+ return true;
+ }
+ } else if (callbacks == &sLiteralExternalString) {
+ if constexpr (encoding == AcceptedEncoding::ASCII) {
+ if (!mozilla::IsAscii(mozilla::Span(chars, len))) {
+ return false;
+ }
+ }
+
+ // The characters represent a literal string constant
+ // compiled into libxul; we can just use it as-is.
+ dest.AssignLiteral(chars, len);
+ return true;
+ }
+
+ return false;
+ }
+
+ public:
+ template <typename T>
+ static MOZ_ALWAYS_INLINE bool MaybeAssignUCStringChars(JSString* s,
+ size_t len, T& dest) {
+ return MaybeAssignStringChars<char16_t, char16_t, AcceptedEncoding::All>(
+ s, len, dest);
+ }
+
+ template <typename T>
+ static MOZ_ALWAYS_INLINE bool MaybeAssignLatin1StringChars(JSString* s,
+ size_t len,
+ T& dest) {
+ return MaybeAssignStringChars<JS::Latin1Char, char, AcceptedEncoding::All>(
+ s, len, dest);
+ }
+
+ template <typename T>
+ static MOZ_ALWAYS_INLINE bool MaybeAssignUTF8StringChars(JSString* s,
+ size_t len,
+ T& dest) {
+ return MaybeAssignStringChars<JS::Latin1Char, char,
+ AcceptedEncoding::ASCII>(s, len, dest);
+ }
+
+ private:
+ struct LiteralExternalString : public JSExternalStringCallbacks {
+ void finalize(JS::Latin1Char* aChars) const override;
+ void finalize(char16_t* aChars) const override;
+ size_t sizeOfBuffer(const JS::Latin1Char* aChars,
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ size_t sizeOfBuffer(const char16_t* aChars,
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ };
+ struct DOMStringExternalString : public JSExternalStringCallbacks {
+ void finalize(JS::Latin1Char* aChars) const override;
+ void finalize(char16_t* aChars) const override;
+ size_t sizeOfBuffer(const JS::Latin1Char* aChars,
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ size_t sizeOfBuffer(const char16_t* aChars,
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+ };
+ static const LiteralExternalString sLiteralExternalString;
+ static const DOMStringExternalString sDOMStringExternalString;
+
+ 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);
+bool NonVoidStringToJsval(JSContext* cx, const 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 StringToJsval(JSContext* cx, const 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);
+}
+
+/**
+ * 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::UCStringBufferToJSVal(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);
+ }
+
+ // 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);
+}
+
+/**
+ * As above, but for nsACString with latin-1 (non-UTF8) content.
+ */
+bool NonVoidLatin1StringToJsval(JSContext* cx, nsACString& str,
+ JS::MutableHandle<JS::Value> rval);
+bool NonVoidLatin1StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval);
+
+inline bool Latin1StringToJsval(JSContext* cx, nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidLatin1StringToJsval(cx, str, rval);
+}
+
+inline bool Latin1StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidLatin1StringToJsval(cx, str, rval);
+}
+
+/**
+ * As above, but for nsACString with UTF-8 content.
+ */
+bool NonVoidUTF8StringToJsval(JSContext* cx, nsACString& str,
+ JS::MutableHandle<JS::Value> rval);
+bool NonVoidUTF8StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval);
+
+inline bool UTF8StringToJsval(JSContext* cx, nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidUTF8StringToJsval(cx, str, rval);
+}
+
+inline bool UTF8StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidUTF8StringToJsval(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 and it has a sandboxPrototype, then return
+ * that prototype.
+ * |aCx| is used for checked unwrapping of the prototype.
+ */
+JSObject* SandboxPrototypeOrNull(JSContext* aCx, 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.
+ */
+inline nsGlobalWindowInner* SandboxWindowOrNull(JSObject* aObj,
+ JSContext* aCx) {
+ JSObject* proto = SandboxPrototypeOrNull(aCx, aObj);
+ return proto ? WindowOrNull(proto) : nullptr;
+}
+
+/**
+ * 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);
+
+// This function may be used off-main-thread.
+void SetPrefableCompileOptions(JS::PrefableCompileOptions& options);
+
+// Modify the provided realm options, consistent with |aIsSystemPrincipal| 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 aSecureContext,
+ bool aForceUTC, bool aAlwaysUseFdlibm,
+ bool aLocaleEnUS);
+
+class ErrorBase {
+ public:
+ nsString mErrorMsg;
+ nsString mFileName;
+ uint32_t mSourceId;
+ // Line number (1-origin).
+ uint32_t mLineNumber;
+ // Column number in UTF-16 code units (1-origin).
+ 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__)) || defined(__loongarch__)
+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 */