summaryrefslogtreecommitdiffstats
path: root/dom/base/ChromeUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/ChromeUtils.cpp')
-rw-r--r--dom/base/ChromeUtils.cpp2220
1 files changed, 2220 insertions, 0 deletions
diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp
new file mode 100644
index 0000000000..be06bb083a
--- /dev/null
+++ b/dom/base/ChromeUtils.cpp
@@ -0,0 +1,2220 @@
+/* -*- 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 "ChromeUtils.h"
+
+#include "JSOracleParent.h"
+#include "js/CallAndConstruct.h" // JS::Call
+#include "js/ColumnNumber.h" // JS::TaggedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin
+#include "js/CharacterEncoding.h"
+#include "js/Date.h" // JS::IsISOStyleDate
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_SetProperty, JS_SetPropertyById, JS::IdVector
+#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor, JS_GetOwnPropertyDescriptorById
+#include "js/SavedFrameAPI.h"
+#include "js/Value.h" // JS::Value, JS::StringValue
+#include "jsfriendapi.h"
+#include "WrapperFactory.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FormAutofillNative.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcInfo.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollingMetrics.h"
+#include "mozilla/SharedStyleSheetCache.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/InProcessParent.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/dom/MediaSessionBinding.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Record.h"
+#include "mozilla/dom/ReportingHeader.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/ipc/UtilityProcessHost.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "mozilla/RemoteDecoderManagerChild.h"
+#include "mozilla/KeySystemConfig.h"
+#include "mozilla/WheelHandlingHelper.h"
+#include "IOActivityMonitor.h"
+#include "nsNativeTheme.h"
+#include "nsThreadUtils.h"
+#include "mozJSModuleLoader.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "nsDocShell.h"
+#include "nsIException.h"
+#include "VsyncSource.h"
+
+#ifdef XP_UNIX
+# include <errno.h>
+# include <unistd.h>
+# include <fcntl.h>
+# include <poll.h>
+# include <sys/wait.h>
+
+# ifdef XP_LINUX
+# include <sys/prctl.h>
+# endif
+#endif
+
+#ifdef MOZ_WMF_CDM
+# include "mozilla/MFCDMParent.h"
+#endif
+
+namespace mozilla::dom {
+
+/* static */
+void ChromeUtils::NondeterministicGetWeakMapKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aMap,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
+ if (!aMap.isObject()) {
+ aRetval.setUndefined();
+ } else {
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> objRet(cx);
+ JS::Rooted<JSObject*> mapObj(cx, &aMap.toObject());
+ if (!JS_NondeterministicGetWeakMapKeys(cx, mapObj, &objRet)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::NondeterministicGetWeakSetKeys(
+ GlobalObject& aGlobal, JS::Handle<JS::Value> aSet,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
+ if (!aSet.isObject()) {
+ aRetval.setUndefined();
+ } else {
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JSObject*> objRet(cx);
+ JS::Rooted<JSObject*> setObj(cx, &aSet.toObject());
+ if (!JS_NondeterministicGetWeakSetKeys(cx, setObj, &objRet)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
+ const ArrayBufferViewOrArrayBuffer& aSource,
+ const Base64URLEncodeOptions& aOptions,
+ nsACString& aResult, ErrorResult& aRv) {
+ auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include
+ : Base64URLEncodePaddingPolicy::Omit;
+ ProcessTypedArrays(
+ aSource, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
+ nsresult rv = mozilla::Base64URLEncode(aData.Length(), aData.Elements(),
+ paddingPolicy, aResult);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResult.Truncate();
+ aRv.Throw(rv);
+ }
+ });
+}
+
+/* static */
+void ChromeUtils::Base64URLDecode(GlobalObject& aGlobal,
+ const nsACString& aString,
+ const Base64URLDecodeOptions& aOptions,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ Base64URLDecodePaddingPolicy paddingPolicy;
+ switch (aOptions.mPadding) {
+ case Base64URLDecodePadding::Require:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Require;
+ break;
+
+ case Base64URLDecodePadding::Ignore:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Ignore;
+ break;
+
+ case Base64URLDecodePadding::Reject:
+ paddingPolicy = Base64URLDecodePaddingPolicy::Reject;
+ break;
+
+ default:
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ FallibleTArray<uint8_t> data;
+ nsresult rv = mozilla::Base64URLDecode(aString, paddingPolicy, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ JS::Rooted<JSObject*> buffer(
+ aGlobal.Context(), ArrayBuffer::Create(aGlobal.Context(), data, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+ aRetval.set(buffer);
+}
+
+/* static */
+void ChromeUtils::ReleaseAssert(GlobalObject& aGlobal, bool aCondition,
+ const nsAString& aMessage) {
+ // If the condition didn't fail, which is the likely case, immediately return.
+ if (MOZ_LIKELY(aCondition)) {
+ return;
+ }
+
+ // Extract the current stack from the JS runtime to embed in the crash reason.
+ nsAutoString filename;
+ uint32_t lineNo = 0;
+
+ if (nsCOMPtr<nsIStackFrame> location = GetCurrentJSStack(1)) {
+ location->GetFilename(aGlobal.Context(), filename);
+ lineNo = location->GetLineNumber(aGlobal.Context());
+ } else {
+ filename.Assign(u"<unknown>"_ns);
+ }
+
+ // Convert to utf-8 for adding as the MozCrashReason.
+ NS_ConvertUTF16toUTF8 filenameUtf8(filename);
+ NS_ConvertUTF16toUTF8 messageUtf8(aMessage);
+
+ // Actually crash.
+ MOZ_CRASH_UNSAFE_PRINTF("Failed ChromeUtils.releaseAssert(\"%s\") @ %s:%u",
+ messageUtf8.get(), filenameUtf8.get(), lineNo);
+}
+
+/* static */
+void ChromeUtils::AddProfilerMarker(
+ GlobalObject& aGlobal, const nsACString& aName,
+ const ProfilerMarkerOptionsOrDouble& aOptions,
+ const Optional<nsACString>& aText) {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ MarkerOptions options;
+
+ MarkerCategory category = ::geckoprofiler::category::JS;
+
+ DOMHighResTimeStamp startTime = 0;
+ uint64_t innerWindowId = 0;
+ if (aOptions.IsDouble()) {
+ startTime = aOptions.GetAsDouble();
+ } else {
+ const ProfilerMarkerOptions& opt = aOptions.GetAsProfilerMarkerOptions();
+ startTime = opt.mStartTime;
+ innerWindowId = opt.mInnerWindowId;
+
+ if (opt.mCaptureStack) {
+ // If we will be capturing a stack, change the category of the
+ // ChromeUtils.addProfilerMarker label automatically added by the webidl
+ // binding from DOM to PROFILER so that this function doesn't appear in
+ // the marker stack.
+ JSContext* cx = aGlobal.Context();
+ ProfilingStack* stack = js::GetContextProfilingStackIfEnabled(cx);
+ if (MOZ_LIKELY(stack)) {
+ uint32_t sp = stack->stackPointer;
+ if (MOZ_LIKELY(sp > 0)) {
+ js::ProfilingStackFrame& frame = stack->frames[sp - 1];
+ if (frame.isLabelFrame() && "ChromeUtils"_ns.Equals(frame.label()) &&
+ "addProfilerMarker"_ns.Equals(frame.dynamicString())) {
+ frame.setLabelCategory(JS::ProfilingCategoryPair::PROFILER);
+ }
+ }
+ }
+
+ options.Set(MarkerStack::Capture());
+ }
+#define BEGIN_CATEGORY(name, labelAsString, color) \
+ if (opt.mCategory.Equals(labelAsString)) { \
+ category = ::geckoprofiler::category::name; \
+ } else
+#define SUBCATEGORY(supercategory, name, labelAsString)
+#define END_CATEGORY
+ MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY)
+#undef BEGIN_CATEGORY
+#undef SUBCATEGORY
+#undef END_CATEGORY
+ {
+ category = ::geckoprofiler::category::OTHER;
+ }
+ }
+ if (startTime) {
+ RefPtr<Performance> performance;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (ownerWindow) {
+ performance = ownerWindow->GetPerformance();
+ }
+ } else {
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+ if (workerPrivate) {
+ performance = workerPrivate->GlobalScope()->GetPerformance();
+ }
+ }
+
+ if (performance) {
+ options.Set(MarkerTiming::IntervalUntilNowFrom(
+ performance->CreationTimeStamp() +
+ TimeDuration::FromMilliseconds(startTime)));
+ } else {
+ options.Set(MarkerTiming::IntervalUntilNowFrom(
+ TimeStamp::ProcessCreation() +
+ TimeDuration::FromMilliseconds(startTime)));
+ }
+ }
+
+ if (innerWindowId) {
+ options.Set(MarkerInnerWindowId(innerWindowId));
+ } else {
+ options.Set(MarkerInnerWindowIdFromJSContext(aGlobal.Context()));
+ }
+
+ {
+ AUTO_PROFILER_STATS(ChromeUtils_AddProfilerMarker);
+ if (aText.WasPassed()) {
+ profiler_add_marker(aName, category, std::move(options),
+ ::geckoprofiler::markers::TextMarker{},
+ aText.Value());
+ } else {
+ profiler_add_marker(aName, category, std::move(options));
+ }
+ }
+}
+
+/* static */
+void ChromeUtils::GetXPCOMErrorName(GlobalObject& aGlobal, uint32_t aErrorCode,
+ nsACString& aRetval) {
+ GetErrorName((nsresult)aErrorCode, aRetval);
+}
+
+/* static */
+void ChromeUtils::WaiveXrays(GlobalObject& aGlobal, JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> value(aGlobal.Context(), aVal);
+ if (!xpc::WrapperFactory::WaiveXrayAndWrap(aGlobal.Context(), &value)) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ } else {
+ aRetval.set(value);
+ }
+}
+
+/* static */
+void ChromeUtils::UnwaiveXrays(GlobalObject& aGlobal,
+ JS::Handle<JS::Value> aVal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ if (!aVal.isObject()) {
+ aRetval.set(aVal);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aGlobal.Context(),
+ js::UncheckedUnwrap(&aVal.toObject()));
+ if (!JS_WrapObject(aGlobal.Context(), &obj)) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ } else {
+ aRetval.setObject(*obj);
+ }
+}
+
+/* static */
+void ChromeUtils::GetClassName(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aObj, bool aUnwrap,
+ nsAString& aRetval) {
+ JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
+ if (aUnwrap) {
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ }
+
+ aRetval = NS_ConvertUTF8toUTF16(nsDependentCString(JS::GetClass(obj)->name));
+}
+
+/* static */
+bool ChromeUtils::IsDOMObject(GlobalObject& aGlobal, JS::Handle<JSObject*> aObj,
+ bool aUnwrap) {
+ JS::Rooted<JSObject*> obj(aGlobal.Context(), aObj);
+ if (aUnwrap) {
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ }
+
+ return mozilla::dom::IsDOMObject(obj);
+}
+
+/* static */
+bool ChromeUtils::IsISOStyleDate(GlobalObject& aGlobal,
+ const nsACString& aStr) {
+ // aStr is a UTF-8 string, however we can cast to JS::Latin1Chars
+ // because JS::IsISOStyleDate handles ASCII only
+ return JS::IsISOStyleDate(aGlobal.Context(),
+ JS::Latin1Chars(aStr.Data(), aStr.Length()));
+}
+
+/* static */
+void ChromeUtils::ShallowClone(GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aObj,
+ JS::Handle<JSObject*> aTarget,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+
+ auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });
+
+ JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
+ JS::RootedVector<JS::Value> values(cx);
+ JS::RootedVector<jsid> valuesIds(cx);
+
+ {
+ // cx represents our current Realm, so it makes sense to use it for the
+ // CheckedUnwrapDynamic call. We do want CheckedUnwrapDynamic, in case
+ // someone is shallow-cloning a Window.
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapDynamic(aObj, cx));
+ if (!obj) {
+ js::ReportAccessDenied(cx);
+ return;
+ }
+
+ if (js::IsScriptedProxy(obj)) {
+ JS_ReportErrorASCII(cx, "Shallow cloning a proxy object is not allowed");
+ return;
+ }
+
+ JSAutoRealm ar(cx, obj);
+
+ if (!JS_Enumerate(cx, obj, &ids) || !values.reserve(ids.length()) ||
+ !valuesIds.reserve(ids.length())) {
+ return;
+ }
+
+ JS::Rooted<Maybe<JS::PropertyDescriptor>> desc(cx);
+ JS::Rooted<JS::PropertyKey> id(cx);
+ for (jsid idVal : ids) {
+ id = idVal;
+ if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) {
+ continue;
+ }
+ if (desc.isNothing() || desc->isAccessorDescriptor()) {
+ continue;
+ }
+ valuesIds.infallibleAppend(id);
+ values.infallibleAppend(desc->value());
+ }
+ }
+
+ JS::Rooted<JSObject*> obj(cx);
+ {
+ Maybe<JSAutoRealm> ar;
+ if (aTarget) {
+ // Our target could be anything, so we want CheckedUnwrapDynamic here.
+ // "cx" represents the current Realm when we were called from bindings, so
+ // we can just use that.
+ JS::Rooted<JSObject*> target(cx, js::CheckedUnwrapDynamic(aTarget, cx));
+ if (!target) {
+ js::ReportAccessDenied(cx);
+ return;
+ }
+ ar.emplace(cx, target);
+ }
+
+ obj = JS_NewPlainObject(cx);
+ if (!obj) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(cx);
+ JS::Rooted<JS::PropertyKey> id(cx);
+ for (uint32_t i = 0; i < valuesIds.length(); i++) {
+ id = valuesIds[i];
+ value = values[i];
+
+ JS_MarkCrossZoneId(cx, id);
+ if (!JS_WrapValue(cx, &value) ||
+ !JS_SetPropertyById(cx, obj, id, value)) {
+ return;
+ }
+ }
+ }
+
+ if (aTarget && !JS_WrapObject(cx, &obj)) {
+ return;
+ }
+
+ cleanup.release();
+ aRetval.set(obj);
+}
+
+namespace {
+class IdleDispatchRunnable final : public IdleRunnable,
+ public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ IdleDispatchRunnable(nsIGlobalObject* aParent, IdleRequestCallback& aCallback)
+ : IdleRunnable("ChromeUtils::IdleDispatch"),
+ mCallback(&aCallback),
+ mParent(aParent) {}
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
+ // See bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ if (mCallback) {
+ CancelTimer();
+
+ auto deadline = mDeadline - TimeStamp::ProcessCreation();
+
+ ErrorResult rv;
+ RefPtr<IdleDeadline> idleDeadline =
+ new IdleDeadline(mParent, mTimedOut, deadline.ToMilliseconds());
+
+ RefPtr<IdleRequestCallback> callback(std::move(mCallback));
+ MOZ_ASSERT(!mCallback);
+ callback->Call(*idleDeadline, "ChromeUtils::IdleDispatch handler");
+ mParent = nullptr;
+ }
+ return NS_OK;
+ }
+
+ void SetDeadline(TimeStamp aDeadline) override { mDeadline = aDeadline; }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ mTimedOut = true;
+ SetDeadline(TimeStamp::Now());
+ return Run();
+ }
+
+ void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override {
+ MOZ_ASSERT(aTarget);
+ MOZ_ASSERT(!mTimer);
+ NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aDelay,
+ nsITimer::TYPE_ONE_SHOT, aTarget);
+ }
+
+ protected:
+ virtual ~IdleDispatchRunnable() { CancelTimer(); }
+
+ private:
+ void CancelTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ }
+
+ RefPtr<IdleRequestCallback> mCallback;
+ nsCOMPtr<nsIGlobalObject> mParent;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ TimeStamp mDeadline{};
+ bool mTimedOut = false;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(IdleDispatchRunnable, IdleRunnable,
+ nsITimerCallback)
+} // anonymous namespace
+
+/* static */
+void ChromeUtils::IdleDispatch(const GlobalObject& aGlobal,
+ IdleRequestCallback& aCallback,
+ const IdleRequestOptions& aOptions,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ auto runnable = MakeRefPtr<IdleDispatchRunnable>(global, aCallback);
+
+ if (aOptions.mTimeout.WasPassed()) {
+ aRv = NS_DispatchToCurrentThreadQueue(
+ runnable.forget(), aOptions.mTimeout.Value(), EventQueuePriority::Idle);
+ } else {
+ aRv = NS_DispatchToCurrentThreadQueue(runnable.forget(),
+ EventQueuePriority::Idle);
+ }
+}
+
+/* static */
+void ChromeUtils::Import(const GlobalObject& aGlobal,
+ const nsACString& aResourceURI,
+ const Optional<JS::Handle<JSObject*>>& aTargetObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ RefPtr moduleloader = mozJSModuleLoader::Get();
+ MOZ_ASSERT(moduleloader);
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("ChromeUtils::Import",
+ OTHER, aResourceURI);
+
+ JSContext* cx = aGlobal.Context();
+
+ JS::Rooted<JSObject*> global(cx);
+ JS::Rooted<JSObject*> exports(cx);
+ nsresult rv = moduleloader->Import(cx, aResourceURI, &global, &exports);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ // Import() on the component loader can return NS_OK while leaving an
+ // exception on the JSContext. Check for that case.
+ if (JS_IsExceptionPending(cx)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ if (aTargetObj.WasPassed()) {
+ if (!JS_AssignObject(cx, aTargetObj.Value(), exports)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ }
+
+ if (!JS_WrapObject(cx, &exports)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aRetval.set(exports);
+}
+
+static mozJSModuleLoader* GetContextualESLoader(
+ const Optional<bool>& aLoadInDevToolsLoader, JSObject* aGlobal) {
+ RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader();
+ // We should load the module in the DevTools loader if:
+ // - ChromeUtils.importESModule's `loadInDevToolsLoader` option is true, or,
+ // - if the callsite is from a module loaded in the DevTools loader and
+ // `loadInDevToolsLoader` isn't an explicit false.
+ bool shouldUseDevToolsLoader =
+ (aLoadInDevToolsLoader.WasPassed() && aLoadInDevToolsLoader.Value()) ||
+ (devToolsModuleloader && !aLoadInDevToolsLoader.WasPassed() &&
+ devToolsModuleloader->IsLoaderGlobal(aGlobal));
+ if (shouldUseDevToolsLoader) {
+ return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+ }
+ return mozJSModuleLoader::Get();
+}
+
+static mozJSModuleLoader* GetModuleLoaderForCurrentGlobal(
+ JSContext* aCx, const GlobalObject& aGlobal,
+ Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
+ aMaybeSyncLoaderScope) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+ if (mozJSModuleLoader::IsSharedSystemGlobal(global)) {
+ return mozJSModuleLoader::Get();
+ }
+ if (mozJSModuleLoader::IsDevToolsLoaderGlobal(global)) {
+ return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+ }
+
+ if (loader::NonSharedGlobalSyncModuleLoaderScope::IsActive()) {
+ mozJSModuleLoader* moduleloader =
+ loader::NonSharedGlobalSyncModuleLoaderScope::ActiveLoader();
+
+ if (!moduleloader->IsLoaderGlobal(global->GetGlobalJSObject())) {
+ JS_ReportErrorASCII(aCx,
+ "global: \"current\" option cannot be used for "
+ "different global while other importESModule "
+ "with global: \"current\" is on the stack");
+ return nullptr;
+ }
+
+ return moduleloader;
+ }
+
+ RefPtr targetModuleLoader = global->GetModuleLoader(aCx);
+ if (!targetModuleLoader) {
+ // Sandbox without associated window returns nullptr for GetModuleLoader.
+ JS_ReportErrorASCII(aCx, "No ModuleLoader found for the current context");
+ return nullptr;
+ }
+
+ if (targetModuleLoader->HasFetchingModules()) {
+ if (!NS_IsMainThread()) {
+ JS_ReportErrorASCII(aCx,
+ "ChromeUtils.importESModule cannot be used in worker "
+ "when there is ongoing dynamic import");
+ return nullptr;
+ }
+
+ if (!mozilla::SpinEventLoopUntil(
+ "importESModule for current global"_ns, [&]() -> bool {
+ return !targetModuleLoader->HasFetchingModules();
+ })) {
+ JS_ReportErrorASCII(aCx, "Failed to wait for ongoing module requests");
+ return nullptr;
+ }
+ }
+
+ aMaybeSyncLoaderScope.emplace(aCx, global);
+ return aMaybeSyncLoaderScope->ActiveLoader();
+}
+
+static mozJSModuleLoader* GetModuleLoaderForOptions(
+ JSContext* aCx, const GlobalObject& aGlobal,
+ const ImportESModuleOptionsDictionary& aOptions,
+ Maybe<loader::NonSharedGlobalSyncModuleLoaderScope>&
+ aMaybeSyncLoaderScope) {
+ if (!aOptions.mGlobal.WasPassed()) {
+ return GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get());
+ }
+
+ switch (aOptions.mGlobal.Value()) {
+ case ImportESModuleTargetGlobal::Shared:
+ return mozJSModuleLoader::Get();
+
+ case ImportESModuleTargetGlobal::Devtools:
+ return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+
+ case ImportESModuleTargetGlobal::Contextual: {
+ if (!NS_IsMainThread()) {
+ return GetModuleLoaderForCurrentGlobal(aCx, aGlobal,
+ aMaybeSyncLoaderScope);
+ }
+
+ RefPtr devToolsModuleloader = mozJSModuleLoader::GetDevToolsLoader();
+ if (devToolsModuleloader &&
+ devToolsModuleloader->IsLoaderGlobal(aGlobal.Get())) {
+ return mozJSModuleLoader::GetOrCreateDevToolsLoader();
+ }
+ return mozJSModuleLoader::Get();
+ }
+
+ case ImportESModuleTargetGlobal::Current:
+ return GetModuleLoaderForCurrentGlobal(aCx, aGlobal,
+ aMaybeSyncLoaderScope);
+
+ default:
+ MOZ_CRASH("Unknown ImportESModuleTargetGlobal");
+ }
+}
+
+static bool ValidateImportOptions(
+ JSContext* aCx, const ImportESModuleOptionsDictionary& aOptions) {
+ if (!NS_IsMainThread() &&
+ (!aOptions.mGlobal.WasPassed() ||
+ (aOptions.mGlobal.Value() != ImportESModuleTargetGlobal::Current &&
+ aOptions.mGlobal.Value() != ImportESModuleTargetGlobal::Contextual))) {
+ JS_ReportErrorASCII(aCx,
+ "ChromeUtils.importESModule: Only { global: "
+ "\"current\" } and { global: \"contextual\" } options "
+ "are supported on worker");
+ return false;
+ }
+
+ if (aOptions.mGlobal.WasPassed() &&
+ aOptions.mLoadInDevToolsLoader.WasPassed()) {
+ JS_ReportErrorASCII(aCx,
+ "global option and loadInDevToolsLoader option "
+ "cannot be used at the same time");
+ return false;
+ }
+
+ return true;
+}
+
+/* static */
+void ChromeUtils::ImportESModule(
+ const GlobalObject& aGlobal, const nsAString& aResourceURI,
+ const ImportESModuleOptionsDictionary& aOptions,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+
+ if (!ValidateImportOptions(cx, aOptions)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope;
+ RefPtr<mozJSModuleLoader> moduleloader =
+ GetModuleLoaderForOptions(cx, aGlobal, aOptions, maybeSyncLoaderScope);
+ if (!moduleloader) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 registryLocation(aResourceURI);
+
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE(
+ "ChromeUtils::ImportESModule", OTHER, registryLocation);
+
+ JS::Rooted<JSObject*> moduleNamespace(cx);
+ nsresult rv =
+ moduleloader->ImportESModule(cx, registryLocation, &moduleNamespace);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+
+ if (!JS_WrapObject(cx, &moduleNamespace)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aRetval.set(moduleNamespace);
+
+ if (maybeSyncLoaderScope) {
+ maybeSyncLoaderScope->Finish();
+ }
+}
+
+// An integer encoding for ImportESModuleOptionsDictionary, to pass the value
+// to the lazy getters.
+class EncodedOptions {
+ public:
+ explicit EncodedOptions(const ImportESModuleOptionsDictionary& aOptions) {
+ uint32_t globalFlag = 0;
+ if (aOptions.mGlobal.WasPassed()) {
+ globalFlag = uint32_t(aOptions.mGlobal.Value()) + 1;
+ }
+
+ uint32_t devtoolsFlag = 0;
+ if (aOptions.mLoadInDevToolsLoader.WasPassed()) {
+ if (aOptions.mLoadInDevToolsLoader.Value()) {
+ devtoolsFlag = DevToolsFlag_True;
+ } else {
+ devtoolsFlag = DevToolsFlag_False;
+ }
+ }
+
+ mValue = globalFlag | devtoolsFlag;
+ }
+
+ explicit EncodedOptions(uint32_t aValue) : mValue(aValue) {}
+
+ int32_t toInt32() const { return int32_t(mValue); }
+
+ void DecodeInto(ImportESModuleOptionsDictionary& aOptions) {
+ uint32_t globalFlag = mValue & GlobalFlag_Mask;
+ if (globalFlag == 0) {
+ aOptions.mGlobal.Reset();
+ } else {
+ aOptions.mGlobal.Construct(ImportESModuleTargetGlobal(globalFlag - 1));
+ }
+
+ uint32_t devtoolsFlag = mValue & DevToolsFlag_Mask;
+ switch (devtoolsFlag) {
+ case DevToolsFlag_NotPassed:
+ aOptions.mLoadInDevToolsLoader.Reset();
+ break;
+ case DevToolsFlag_False:
+ aOptions.mLoadInDevToolsLoader.Construct(false);
+ break;
+ case DevToolsFlag_True:
+ aOptions.mLoadInDevToolsLoader.Construct(true);
+ break;
+ default:
+ MOZ_CRASH("Unknown DevToolsFlag");
+ }
+ }
+
+ private:
+ static constexpr uint32_t GlobalFlag_Mask = 0xF;
+
+ static constexpr uint32_t DevToolsFlag_NotPassed = 0x00;
+ static constexpr uint32_t DevToolsFlag_False = 0x10;
+ static constexpr uint32_t DevToolsFlag_True = 0x20;
+ static constexpr uint32_t DevToolsFlag_Mask = 0x0F0;
+
+ uint32_t mValue = 0;
+};
+
+namespace lazy_getter {
+
+// The property id of the getter.
+// Used by all lazy getters.
+static const size_t SLOT_ID = 0;
+
+// The URI of the module to import.
+// Used by ChromeUtils.defineModuleGetter and ChromeUtils.defineESModuleGetters.
+static const size_t SLOT_URI = 1;
+
+// An array object that contians values for PARAM_INDEX_TARGET and
+// PARAM_INDEX_LAMBDA.
+// Used by ChromeUtils.defineLazyGetter.
+static const size_t SLOT_PARAMS = 1;
+
+// The EncodedOptions value.
+// Used by ChromeUtils.defineESModuleGetters.
+static const size_t SLOT_OPTIONS = 2;
+
+static const size_t PARAM_INDEX_TARGET = 0;
+static const size_t PARAM_INDEX_LAMBDA = 1;
+static const size_t PARAMS_COUNT = 2;
+
+static bool ExtractArgs(JSContext* aCx, JS::CallArgs& aArgs,
+ JS::MutableHandle<JSObject*> aCallee,
+ JS::MutableHandle<JSObject*> aThisObj,
+ JS::MutableHandle<jsid> aId) {
+ aCallee.set(&aArgs.callee());
+
+ JS::Handle<JS::Value> thisv = aArgs.thisv();
+ if (!thisv.isObject()) {
+ JS_ReportErrorASCII(aCx, "Invalid target object");
+ return false;
+ }
+
+ aThisObj.set(&thisv.toObject());
+
+ JS::Rooted<JS::Value> id(aCx,
+ js::GetFunctionNativeReserved(aCallee, SLOT_ID));
+ MOZ_ALWAYS_TRUE(JS_ValueToId(aCx, id, aId));
+ return true;
+}
+
+static bool JSLazyGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> unused(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &unused, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> paramsVal(
+ aCx, js::GetFunctionNativeReserved(callee, SLOT_PARAMS));
+ if (paramsVal.isUndefined()) {
+ args.rval().setUndefined();
+ return true;
+ }
+ // Avoid calling the lambda multiple times, in case of:
+ // * the getter function is retrieved from property descriptor and called
+ // * the lambda gets the property again
+ // * the getter function throws and accessed again
+ js::SetFunctionNativeReserved(callee, SLOT_PARAMS, JS::UndefinedHandleValue);
+
+ JS::Rooted<JSObject*> paramsObj(aCx, &paramsVal.toObject());
+
+ JS::Rooted<JS::Value> targetVal(aCx);
+ JS::Rooted<JS::Value> lambdaVal(aCx);
+ if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_TARGET, &targetVal)) {
+ return false;
+ }
+ if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_LAMBDA, &lambdaVal)) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> targetObj(aCx, &targetVal.toObject());
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS::Call(aCx, targetObj, lambdaVal, JS::HandleValueArray::empty(),
+ &value)) {
+ return false;
+ }
+
+ if (!JS_DefinePropertyById(aCx, targetObj, id, value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ args.rval().set(value);
+ return true;
+}
+
+static bool DefineLazyGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::Value> aName,
+ JS::Handle<JSObject*> aLambda) {
+ JS::Rooted<jsid> id(aCx);
+ if (!JS_ValueToId(aCx, aName, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSLazyGetter, 0, 0, id)));
+ if (!getter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ JS::RootedVector<JS::Value> params(aCx);
+ if (!params.resize(PARAMS_COUNT)) {
+ return false;
+ }
+ params[PARAM_INDEX_TARGET].setObject(*aTarget);
+ params[PARAM_INDEX_LAMBDA].setObject(*aLambda);
+ JS::Rooted<JSObject*> paramsObj(aCx, JS::NewArrayObject(aCx, params));
+ if (!paramsObj) {
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, aName);
+ js::SetFunctionNativeReserved(getter, SLOT_PARAMS,
+ JS::ObjectValue(*paramsObj));
+
+ return JS_DefinePropertyById(aCx, aTarget, id, getter, nullptr,
+ JSPROP_ENUMERATE);
+}
+
+enum class ModuleType { JSM, ESM };
+
+static bool ModuleGetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
+ ModuleType aType) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> thisObj(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+ return false;
+ }
+
+ JS::Rooted<JSString*> moduleURI(
+ aCx, js::GetFunctionNativeReserved(callee, SLOT_URI).toString());
+ JS::UniqueChars bytes = JS_EncodeStringToUTF8(aCx, moduleURI);
+ if (!bytes) {
+ return false;
+ }
+ nsDependentCString uri(bytes.get());
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (aType == ModuleType::JSM) {
+ RefPtr moduleloader = mozJSModuleLoader::Get();
+ MOZ_ASSERT(moduleloader);
+
+ JS::Rooted<JSObject*> moduleGlobal(aCx);
+ JS::Rooted<JSObject*> moduleExports(aCx);
+ nsresult rv = moduleloader->Import(aCx, uri, &moduleGlobal, &moduleExports);
+ if (NS_FAILED(rv)) {
+ Throw(aCx, rv);
+ return false;
+ }
+
+ // JSM's exports is from the same realm.
+ if (!JS_GetPropertyById(aCx, moduleExports, id, &value)) {
+ return false;
+ }
+ } else {
+ EncodedOptions encodedOptions(
+ js::GetFunctionNativeReserved(callee, SLOT_OPTIONS).toInt32());
+
+ ImportESModuleOptionsDictionary options;
+ encodedOptions.DecodeInto(options);
+
+ if (!ValidateImportOptions(aCx, options)) {
+ return false;
+ }
+
+ GlobalObject global(aCx, callee);
+
+ Maybe<loader::NonSharedGlobalSyncModuleLoaderScope> maybeSyncLoaderScope;
+ RefPtr<mozJSModuleLoader> moduleloader =
+ GetModuleLoaderForOptions(aCx, global, options, maybeSyncLoaderScope);
+ if (!moduleloader) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> moduleNamespace(aCx);
+ nsresult rv = moduleloader->ImportESModule(aCx, uri, &moduleNamespace);
+ if (NS_FAILED(rv)) {
+ Throw(aCx, rv);
+ return false;
+ }
+
+ // ESM's namespace is from the module's realm.
+ {
+ JSAutoRealm ar(aCx, moduleNamespace);
+ if (!JS_GetPropertyById(aCx, moduleNamespace, id, &value)) {
+ return false;
+ }
+ }
+ if (!JS_WrapValue(aCx, &value)) {
+ return false;
+ }
+
+ if (maybeSyncLoaderScope) {
+ maybeSyncLoaderScope->Finish();
+ }
+ }
+
+ if (!JS_DefinePropertyById(aCx, thisObj, id, value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ args.rval().set(value);
+ return true;
+}
+
+static bool JSModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleGetterImpl(aCx, aArgc, aVp, ModuleType::JSM);
+}
+
+static bool ESModuleGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleGetterImpl(aCx, aArgc, aVp, ModuleType::ESM);
+}
+
+static bool ModuleSetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ JS::Rooted<JSObject*> callee(aCx);
+ JS::Rooted<JSObject*> thisObj(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
+ return false;
+ }
+
+ return JS_DefinePropertyById(aCx, thisObj, id, args.get(0), JSPROP_ENUMERATE);
+}
+
+static bool JSModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleSetterImpl(aCx, aArgc, aVp);
+}
+
+static bool ESModuleSetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
+ return ModuleSetterImpl(aCx, aArgc, aVp);
+}
+
+static bool DefineJSModuleGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ const nsAString& aId,
+ const nsAString& aResourceURI) {
+ JS::Rooted<JS::Value> uri(aCx);
+ JS::Rooted<JS::Value> idValue(aCx);
+ JS::Rooted<jsid> id(aCx);
+ if (!xpc::NonVoidStringToJsval(aCx, aResourceURI, &uri) ||
+ !xpc::NonVoidStringToJsval(aCx, aId, &idValue) ||
+ !JS_ValueToId(aCx, idValue, &id)) {
+ return false;
+ }
+ idValue = js::IdToValue(id);
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSModuleGetter, 0, 0, id)));
+
+ JS::Rooted<JSObject*> setter(
+ aCx, JS_GetFunctionObject(
+ js::NewFunctionByIdWithReserved(aCx, JSModuleSetter, 0, 0, id)));
+
+ if (!getter || !setter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, idValue);
+ js::SetFunctionNativeReserved(setter, SLOT_ID, idValue);
+
+ js::SetFunctionNativeReserved(getter, SLOT_URI, uri);
+
+ return JS_DefinePropertyById(aCx, aTarget, id, getter, setter,
+ JSPROP_ENUMERATE);
+}
+
+static bool DefineESModuleGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::Handle<JS::Value> aResourceURI,
+ const EncodedOptions& encodedOptions) {
+ JS::Rooted<JS::Value> idVal(aCx, JS::StringValue(aId.toString()));
+
+ JS::Rooted<JS::Value> optionsVal(aCx,
+ JS::Int32Value(encodedOptions.toInt32()));
+
+ JS::Rooted<JSObject*> getter(
+ aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
+ aCx, ESModuleGetter, 0, 0, aId)));
+
+ JS::Rooted<JSObject*> setter(
+ aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved(
+ aCx, ESModuleSetter, 0, 0, aId)));
+
+ if (!getter || !setter) {
+ JS_ReportOutOfMemory(aCx);
+ return false;
+ }
+
+ js::SetFunctionNativeReserved(getter, SLOT_ID, idVal);
+ js::SetFunctionNativeReserved(setter, SLOT_ID, idVal);
+
+ js::SetFunctionNativeReserved(getter, SLOT_URI, aResourceURI);
+
+ js::SetFunctionNativeReserved(getter, SLOT_OPTIONS, optionsVal);
+
+ return JS_DefinePropertyById(aCx, aTarget, aId, getter, setter,
+ JSPROP_ENUMERATE);
+}
+
+} // namespace lazy_getter
+
+/* static */
+void ChromeUtils::DefineLazyGetter(const GlobalObject& aGlobal,
+ JS::Handle<JSObject*> aTarget,
+ JS::Handle<JS::Value> aName,
+ JS::Handle<JSObject*> aLambda,
+ ErrorResult& aRv) {
+ JSContext* cx = aGlobal.Context();
+ if (!lazy_getter::DefineLazyGetter(cx, aTarget, aName, aLambda)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+}
+
+/* static */
+void ChromeUtils::DefineModuleGetter(const GlobalObject& global,
+ JS::Handle<JSObject*> target,
+ const nsAString& id,
+ const nsAString& resourceURI,
+ ErrorResult& aRv) {
+ if (!lazy_getter::DefineJSModuleGetter(global.Context(), target, id,
+ resourceURI)) {
+ aRv.NoteJSContextException(global.Context());
+ }
+}
+
+/* static */
+void ChromeUtils::DefineESModuleGetters(
+ const GlobalObject& global, JS::Handle<JSObject*> target,
+ JS::Handle<JSObject*> modules,
+ const ImportESModuleOptionsDictionary& aOptions, ErrorResult& aRv) {
+ JSContext* cx = global.Context();
+
+ JS::Rooted<JS::IdVector> props(cx, JS::IdVector(cx));
+ if (!JS_Enumerate(cx, modules, &props)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ EncodedOptions encodedOptions(aOptions);
+
+ JS::Rooted<JS::PropertyKey> prop(cx);
+ JS::Rooted<JS::Value> resourceURIVal(cx);
+ for (JS::PropertyKey tmp : props) {
+ prop = tmp;
+
+ if (!prop.isString()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (!JS_GetPropertyById(cx, modules, prop, &resourceURIVal)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+
+ if (!lazy_getter::DefineESModuleGetter(cx, target, prop, resourceURIVal,
+ encodedOptions)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ }
+}
+
+#ifdef XP_UNIX
+/* static */
+void ChromeUtils::GetLibcConstants(const GlobalObject&,
+ LibcConstants& aConsts) {
+ aConsts.mEINTR.Construct(EINTR);
+ aConsts.mEACCES.Construct(EACCES);
+ aConsts.mEAGAIN.Construct(EAGAIN);
+ aConsts.mEINVAL.Construct(EINVAL);
+ aConsts.mENOSYS.Construct(ENOSYS);
+
+ aConsts.mF_SETFD.Construct(F_SETFD);
+ aConsts.mF_SETFL.Construct(F_SETFL);
+
+ aConsts.mFD_CLOEXEC.Construct(FD_CLOEXEC);
+
+ aConsts.mAT_EACCESS.Construct(AT_EACCESS);
+
+ aConsts.mO_CREAT.Construct(O_CREAT);
+ aConsts.mO_NONBLOCK.Construct(O_NONBLOCK);
+ aConsts.mO_WRONLY.Construct(O_WRONLY);
+
+ aConsts.mPOLLERR.Construct(POLLERR);
+ aConsts.mPOLLHUP.Construct(POLLHUP);
+ aConsts.mPOLLIN.Construct(POLLIN);
+ aConsts.mPOLLNVAL.Construct(POLLNVAL);
+ aConsts.mPOLLOUT.Construct(POLLOUT);
+
+ aConsts.mWNOHANG.Construct(WNOHANG);
+
+# ifdef XP_LINUX
+ aConsts.mPR_CAPBSET_READ.Construct(PR_CAPBSET_READ);
+# endif
+}
+#endif
+
+/* static */
+void ChromeUtils::OriginAttributesToSuffix(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ nsCString& aSuffix)
+
+{
+ OriginAttributes attrs(aAttrs);
+ attrs.CreateSuffix(aSuffix);
+}
+
+/* static */
+bool ChromeUtils::OriginAttributesMatchPattern(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ const dom::OriginAttributesPatternDictionary& aPattern) {
+ OriginAttributes attrs(aAttrs);
+ OriginAttributesPattern pattern(aPattern);
+ return pattern.Matches(attrs);
+}
+
+/* static */
+void ChromeUtils::CreateOriginAttributesFromOrigin(
+ dom::GlobalObject& aGlobal, const nsAString& aOrigin,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
+ OriginAttributes attrs;
+ nsAutoCString suffix;
+ if (!attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(aOrigin), suffix)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aAttrs = attrs;
+}
+
+/* static */
+void ChromeUtils::CreateOriginAttributesFromOriginSuffix(
+ dom::GlobalObject& aGlobal, const nsAString& aSuffix,
+ dom::OriginAttributesDictionary& aAttrs, ErrorResult& aRv) {
+ OriginAttributes attrs;
+ nsAutoCString suffix;
+ if (!attrs.PopulateFromSuffix(NS_ConvertUTF16toUTF8(aSuffix))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aAttrs = attrs;
+}
+
+/* static */
+void ChromeUtils::FillNonDefaultOriginAttributes(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs,
+ dom::OriginAttributesDictionary& aNewAttrs) {
+ aNewAttrs = aAttrs;
+}
+
+/* static */
+bool ChromeUtils::IsOriginAttributesEqual(
+ dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB) {
+ return IsOriginAttributesEqual(aA, aB);
+}
+
+/* static */
+bool ChromeUtils::IsOriginAttributesEqual(
+ const dom::OriginAttributesDictionary& aA,
+ const dom::OriginAttributesDictionary& aB) {
+ return aA == aB;
+}
+
+/* static */
+void ChromeUtils::GetBaseDomainFromPartitionKey(dom::GlobalObject& aGlobal,
+ const nsAString& aPartitionKey,
+ nsAString& aBaseDomain,
+ ErrorResult& aRv) {
+ nsString scheme;
+ nsString pkBaseDomain;
+ int32_t port;
+
+ if (!mozilla::OriginAttributes::ParsePartitionKey(aPartitionKey, scheme,
+ pkBaseDomain, port)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aBaseDomain = pkBaseDomain;
+}
+
+/* static */
+void ChromeUtils::GetPartitionKeyFromURL(dom::GlobalObject& aGlobal,
+ const nsAString& aURL,
+ nsAString& aPartitionKey,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
+ if (NS_SUCCEEDED(rv) && uri->SchemeIs("chrome")) {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPartitionKey.Truncate();
+ aRv.Throw(rv);
+ return;
+ }
+
+ mozilla::OriginAttributes attrs;
+ attrs.SetPartitionKey(uri);
+
+ aPartitionKey = attrs.mPartitionKey;
+}
+
+#ifdef NIGHTLY_BUILD
+/* static */
+void ChromeUtils::GetRecentJSDevError(GlobalObject& aGlobal,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aRv) {
+ aRetval.setUndefined();
+ auto runtime = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(runtime);
+
+ auto cx = aGlobal.Context();
+ if (!runtime->GetRecentDevError(cx, aRetval)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+}
+
+/* static */
+void ChromeUtils::ClearRecentJSDevError(GlobalObject&) {
+ auto runtime = CycleCollectedJSRuntime::Get();
+ MOZ_ASSERT(runtime);
+
+ runtime->ClearRecentDevError();
+}
+#endif // NIGHTLY_BUILD
+
+void ChromeUtils::ClearStyleSheetCacheByPrincipal(GlobalObject&,
+ nsIPrincipal* aForPrincipal) {
+ SharedStyleSheetCache::Clear(aForPrincipal);
+}
+
+void ChromeUtils::ClearStyleSheetCacheByBaseDomain(
+ GlobalObject&, const nsACString& aBaseDomain) {
+ SharedStyleSheetCache::Clear(nullptr, &aBaseDomain);
+}
+
+void ChromeUtils::ClearStyleSheetCache(GlobalObject&) {
+ SharedStyleSheetCache::Clear();
+}
+
+#define PROCTYPE_TO_WEBIDL_CASE(_procType, _webidl) \
+ case mozilla::ProcType::_procType: \
+ return WebIDLProcType::_webidl
+
+static WebIDLProcType ProcTypeToWebIDL(mozilla::ProcType aType) {
+ // Max is the value of the last enum, not the length, so add one.
+ static_assert(
+ WebIDLProcTypeValues::Count == static_cast<size_t>(ProcType::Max) + 1,
+ "In order for this static cast to be okay, "
+ "WebIDLProcType must match ProcType exactly");
+
+ // These must match the similar ones in E10SUtils.sys.mjs, RemoteTypes.h,
+ // ProcInfo.h and ChromeUtils.webidl
+ switch (aType) {
+ PROCTYPE_TO_WEBIDL_CASE(Web, Web);
+ PROCTYPE_TO_WEBIDL_CASE(WebIsolated, WebIsolated);
+ PROCTYPE_TO_WEBIDL_CASE(File, File);
+ PROCTYPE_TO_WEBIDL_CASE(Extension, Extension);
+ PROCTYPE_TO_WEBIDL_CASE(PrivilegedAbout, Privilegedabout);
+ PROCTYPE_TO_WEBIDL_CASE(PrivilegedMozilla, Privilegedmozilla);
+ PROCTYPE_TO_WEBIDL_CASE(WebCOOPCOEP, WithCoopCoep);
+ PROCTYPE_TO_WEBIDL_CASE(WebServiceWorker, WebServiceWorker);
+
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ PROCTYPE_TO_WEBIDL_CASE(procinfo_typename, webidl_typename);
+#define SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# define SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#include "mozilla/GeckoProcessTypes.h"
+#undef SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# undef SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#undef GECKO_PROCESS_TYPE
+
+ PROCTYPE_TO_WEBIDL_CASE(Preallocated, Preallocated);
+ PROCTYPE_TO_WEBIDL_CASE(Unknown, Unknown);
+ }
+
+ MOZ_ASSERT(false, "Unhandled case in ProcTypeToWebIDL");
+ return WebIDLProcType::Unknown;
+}
+
+#undef PROCTYPE_TO_WEBIDL_CASE
+
+/* static */
+already_AddRefed<Promise> ChromeUtils::RequestProcInfo(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ // This function will use IPDL to enable threads info on macOS
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1529023
+ if (!XRE_IsParentProcess()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ // Prepare the JS promise that will hold our response.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+
+ // Get a list of processes to examine and pre-fill them with available info.
+ // Note that this is subject to race conditions: just because we have a
+ // process in the list doesn't mean that the process will still be alive when
+ // we attempt to get its information. Followup code MUST be able to fail
+ // gracefully on some processes and still return whichever information is
+ // available.
+
+ // Get all the content parents.
+ // Note that this array includes even the long dead content parents, so we
+ // might have some garbage, especially with Fission.
+ // SAFETY NOTE: `contentParents` is only valid if used synchronously.
+ // Anything else and you may end up dealing with dangling pointers.
+ nsTArray<ContentParent*> contentParents;
+ ContentParent::GetAll(contentParents);
+
+ // Prepare our background request.
+ // We reserve one more slot for the browser process itself.
+ nsTArray<ProcInfoRequest> requests(contentParents.Length() + 1);
+ // Requesting process info for the browser process itself.
+ requests.EmplaceBack(
+ /* aPid = */ base::GetCurrentProcId(),
+ /* aProcessType = */ ProcType::Browser,
+ /* aOrigin = */ ""_ns,
+ /* aWindowInfo = */ nsTArray<WindowInfo>(),
+ /* aUtilityInfo = */ nsTArray<UtilityInfo>());
+
+ // First handle non-ContentParent processes.
+ mozilla::ipc::GeckoChildProcessHost::GetAll(
+ [&requests](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) {
+ base::ProcessId childPid = aGeckoProcess->GetChildProcessId();
+ if (childPid == 0) {
+ // Something went wrong with this process, it may be dead already,
+ // fail gracefully.
+ return;
+ }
+ mozilla::ProcType type = mozilla::ProcType::Unknown;
+
+ switch (aGeckoProcess->GetProcessType()) {
+ case GeckoProcessType::GeckoProcessType_Content: {
+ // These processes are handled separately.
+ return;
+ }
+
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ case GeckoProcessType::GeckoProcessType_##enum_name: { \
+ type = mozilla::ProcType::procinfo_typename; \
+ break; \
+ }
+#define SKIP_PROCESS_TYPE_CONTENT
+#ifndef MOZ_ENABLE_FORKSERVER
+# define SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#include "mozilla/GeckoProcessTypes.h"
+#ifndef MOZ_ENABLE_FORKSERVER
+# undef SKIP_PROCESS_TYPE_FORKSERVER
+#endif // MOZ_ENABLE_FORKSERVER
+#undef SKIP_PROCESS_TYPE_CONTENT
+#undef GECKO_PROCESS_TYPE
+ default:
+ // Leave the default Unknown value in |type|.
+ break;
+ }
+
+ // Attach utility actor information to the process.
+ nsTArray<UtilityInfo> utilityActors;
+ if (aGeckoProcess->GetProcessType() ==
+ GeckoProcessType::GeckoProcessType_Utility) {
+ RefPtr<mozilla::ipc::UtilityProcessManager> upm =
+ mozilla::ipc::UtilityProcessManager::GetSingleton();
+ if (!utilityActors.AppendElements(upm->GetActors(aGeckoProcess),
+ fallible)) {
+ NS_WARNING("Error adding actors");
+ return;
+ }
+ }
+
+ requests.EmplaceBack(
+ /* aPid = */ childPid,
+ /* aProcessType = */ type,
+ /* aOrigin = */ ""_ns,
+ /* aWindowInfo = */ nsTArray<WindowInfo>(), // Without a
+ // ContentProcess, no
+ // DOM windows.
+ /* aUtilityInfo = */ std::move(utilityActors),
+ /* aChild = */ 0 // Without a ContentProcess, no ChildId.
+#ifdef XP_MACOSX
+ ,
+ /* aChildTask = */ aGeckoProcess->GetChildTask()
+#endif // XP_MACOSX
+ );
+ });
+
+ // Now handle ContentParents.
+ for (const auto* contentParent : contentParents) {
+ if (!contentParent || !contentParent->Process()) {
+ // Presumably, the process is dead or dying.
+ continue;
+ }
+ base::ProcessId pid = contentParent->Process()->GetChildProcessId();
+ if (pid == 0) {
+ // Presumably, the process is dead or dying.
+ continue;
+ }
+ if (contentParent->Process()->GetProcessType() !=
+ GeckoProcessType::GeckoProcessType_Content) {
+ // We're probably racing against a process changing type.
+ // We'll get it in the next call, skip it for the moment.
+ continue;
+ }
+
+ // Since this code is executed synchronously on the main thread,
+ // processes cannot die while we're in this loop.
+ mozilla::ProcType type = mozilla::ProcType::Unknown;
+
+ // Convert the remoteType into a ProcType.
+ // Ideally, the remoteType should be strongly typed
+ // upstream, this would make the conversion less brittle.
+ const nsAutoCString remoteType(contentParent->GetRemoteType());
+ if (StringBeginsWith(remoteType, FISSION_WEB_REMOTE_TYPE)) {
+ // WARNING: Do not change the order, as
+ // `DEFAULT_REMOTE_TYPE` is a prefix of
+ // `FISSION_WEB_REMOTE_TYPE`.
+ type = mozilla::ProcType::WebIsolated;
+ } else if (StringBeginsWith(remoteType, SERVICEWORKER_REMOTE_TYPE)) {
+ type = mozilla::ProcType::WebServiceWorker;
+ } else if (StringBeginsWith(remoteType,
+ WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) {
+ type = mozilla::ProcType::WebCOOPCOEP;
+ } else if (remoteType == FILE_REMOTE_TYPE) {
+ type = mozilla::ProcType::File;
+ } else if (remoteType == EXTENSION_REMOTE_TYPE) {
+ type = mozilla::ProcType::Extension;
+ } else if (remoteType == PRIVILEGEDABOUT_REMOTE_TYPE) {
+ type = mozilla::ProcType::PrivilegedAbout;
+ } else if (remoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) {
+ type = mozilla::ProcType::PrivilegedMozilla;
+ } else if (remoteType == PREALLOC_REMOTE_TYPE) {
+ type = mozilla::ProcType::Preallocated;
+ } else if (StringBeginsWith(remoteType, DEFAULT_REMOTE_TYPE)) {
+ type = mozilla::ProcType::Web;
+ } else {
+ MOZ_CRASH_UNSAFE_PRINTF("Unknown remoteType '%s'", remoteType.get());
+ }
+
+ // By convention, everything after '=' is the origin.
+ nsAutoCString origin;
+ nsACString::const_iterator cursor;
+ nsACString::const_iterator end;
+ remoteType.BeginReading(cursor);
+ remoteType.EndReading(end);
+ if (FindCharInReadable('=', cursor, end)) {
+ origin = Substring(++cursor, end);
+ }
+
+ // Attach DOM window information to the process.
+ nsTArray<WindowInfo> windows;
+ for (const auto& browserParentWrapperKey :
+ contentParent->ManagedPBrowserParent()) {
+ for (const auto& windowGlobalParentWrapperKey :
+ browserParentWrapperKey->ManagedPWindowGlobalParent()) {
+ // WindowGlobalParent is the only immediate subclass of
+ // PWindowGlobalParent.
+ auto* windowGlobalParent =
+ static_cast<WindowGlobalParent*>(windowGlobalParentWrapperKey);
+
+ nsString documentTitle;
+ windowGlobalParent->GetDocumentTitle(documentTitle);
+ WindowInfo* window = windows.EmplaceBack(
+ fallible,
+ /* aOuterWindowId = */ windowGlobalParent->OuterWindowId(),
+ /* aDocumentURI = */ windowGlobalParent->GetDocumentURI(),
+ /* aDocumentTitle = */ std::move(documentTitle),
+ /* aIsProcessRoot = */ windowGlobalParent->IsProcessRoot(),
+ /* aIsInProcess = */ windowGlobalParent->IsInProcess());
+ if (!window) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ }
+ }
+ requests.EmplaceBack(
+ /* aPid = */ pid,
+ /* aProcessType = */ type,
+ /* aOrigin = */ origin,
+ /* aWindowInfo = */ std::move(windows),
+ /* aUtilityInfo = */ nsTArray<UtilityInfo>(),
+ /* aChild = */ contentParent->ChildID()
+#ifdef XP_MACOSX
+ ,
+ /* aChildTask = */ contentParent->Process()->GetChildTask()
+#endif // XP_MACOSX
+ );
+ }
+
+ // Now place background request.
+ RefPtr<nsISerialEventTarget> target = global->SerialEventTarget();
+ mozilla::GetProcInfo(std::move(requests))
+ ->Then(
+ target, __func__,
+ [target,
+ domPromise](const HashMap<base::ProcessId, ProcInfo>& aSysProcInfo) {
+ ParentProcInfoDictionary parentInfo;
+ if (aSysProcInfo.count() == 0) {
+ // For some reason, we couldn't get *any* info.
+ // Maybe a sandboxing issue?
+ domPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ nsTArray<ChildProcInfoDictionary> childrenInfo(
+ aSysProcInfo.count() - 1);
+ for (auto iter = aSysProcInfo.iter(); !iter.done(); iter.next()) {
+ const auto& sysProcInfo = iter.get().value();
+ nsresult rv;
+ if (sysProcInfo.type == ProcType::Browser) {
+ rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, &parentInfo);
+ if (NS_FAILED(rv)) {
+ // Failing to copy? That's probably not something from we can
+ // (or should) try to recover gracefully.
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ MOZ_ASSERT(sysProcInfo.childId == 0);
+ MOZ_ASSERT(sysProcInfo.origin.IsEmpty());
+ } else {
+ mozilla::dom::ChildProcInfoDictionary* childInfo =
+ childrenInfo.AppendElement(fallible);
+ if (!childInfo) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ rv = mozilla::CopySysProcInfoToDOM(sysProcInfo, childInfo);
+ if (NS_FAILED(rv)) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ // Copy Firefox info.
+ childInfo->mChildID = sysProcInfo.childId;
+ childInfo->mOrigin = sysProcInfo.origin;
+ childInfo->mType = ProcTypeToWebIDL(sysProcInfo.type);
+
+ for (const auto& source : sysProcInfo.windows) {
+ auto* dest = childInfo->mWindows.AppendElement(fallible);
+ if (!dest) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ dest->mOuterWindowId = source.outerWindowId;
+ dest->mDocumentURI = source.documentURI;
+ dest->mDocumentTitle = source.documentTitle;
+ dest->mIsProcessRoot = source.isProcessRoot;
+ dest->mIsInProcess = source.isInProcess;
+ }
+
+ if (sysProcInfo.type == ProcType::Utility) {
+ for (const auto& source : sysProcInfo.utilityActors) {
+ auto* dest =
+ childInfo->mUtilityActors.AppendElement(fallible);
+ if (!dest) {
+ domPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ dest->mActorName = source.actorName;
+ }
+ }
+ }
+ }
+
+ // Attach the children to the parent.
+ mozilla::dom::Sequence<mozilla::dom::ChildProcInfoDictionary>
+ children(std::move(childrenInfo));
+ parentInfo.mChildren = std::move(children);
+ domPromise->MaybeResolve(parentInfo);
+ },
+ [domPromise](nsresult aRv) { domPromise->MaybeReject(aRv); });
+ MOZ_ASSERT(domPromise);
+
+ // sending back the promise instance
+ return domPromise.forget();
+}
+
+/* static */
+bool ChromeUtils::VsyncEnabled(GlobalObject& aGlobal) {
+ return mozilla::gfx::VsyncSource::GetFastestVsyncRate().isSome();
+}
+
+void ChromeUtils::SetPerfStatsCollectionMask(GlobalObject& aGlobal,
+ uint64_t aMask) {
+ PerfStats::SetCollectionMask(static_cast<PerfStats::MetricMask>(aMask));
+}
+
+already_AddRefed<Promise> ChromeUtils::CollectPerfStats(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ // Creating a JS promise
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<PerfStats::PerfStatsPromise> extPromise =
+ PerfStats::CollectPerfStatsJSON();
+
+ extPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const nsCString& aResult) {
+ promise->MaybeResolve(NS_ConvertUTF8toUTF16(aResult));
+ },
+ [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });
+
+ return promise.forget();
+}
+
+constexpr auto kSkipSelfHosted = JS::SavedFrameSelfHosted::Exclude;
+
+/* static */
+void ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal,
+ nsIPrincipal* aPrincipal,
+ JS::MutableHandle<JSObject*> aRetval) {
+ JSContext* cx = aGlobal.Context();
+
+ auto* principals = nsJSPrincipals::get(aPrincipal);
+
+ JS::StackCapture captureMode(JS::FirstSubsumedFrame(cx, principals));
+
+ JS::Rooted<JSObject*> frame(cx);
+ if (!JS::CaptureCurrentStack(cx, &frame, std::move(captureMode))) {
+ JS_ClearPendingException(cx);
+ aRetval.set(nullptr);
+ return;
+ }
+
+ // FirstSubsumedFrame gets us a stack which stops at the first principal which
+ // is subsumed by the given principal. That means that we may have a lot of
+ // privileged frames that we don't care about at the top of the stack, though.
+ // We need to filter those out to get the frame we actually want.
+ aRetval.set(
+ js::GetFirstSubsumedSavedFrame(cx, principals, frame, kSkipSelfHosted));
+}
+
+/* static */
+void ChromeUtils::CreateError(const GlobalObject& aGlobal,
+ const nsAString& aMessage,
+ JS::Handle<JSObject*> aStack,
+ JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) {
+ if (aStack && !JS::IsMaybeWrappedSavedFrame(aStack)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ JSContext* cx = aGlobal.Context();
+
+ auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); });
+
+ JS::Rooted<JSObject*> retVal(cx);
+ {
+ JS::Rooted<JSString*> fileName(cx, JS_GetEmptyString(cx));
+ uint32_t line = 0;
+ JS::TaggedColumnNumberOneOrigin column;
+
+ Maybe<JSAutoRealm> ar;
+ JS::Rooted<JSObject*> stack(cx);
+ if (aStack) {
+ stack = UncheckedUnwrap(aStack);
+ ar.emplace(cx, stack);
+
+ JSPrincipals* principals =
+ JS::GetRealmPrincipals(js::GetContextRealm(cx));
+ if (JS::GetSavedFrameLine(cx, principals, stack, &line) !=
+ JS::SavedFrameResult::Ok ||
+ JS::GetSavedFrameColumn(cx, principals, stack, &column) !=
+ JS::SavedFrameResult::Ok ||
+ JS::GetSavedFrameSource(cx, principals, stack, &fileName) !=
+ JS::SavedFrameResult::Ok) {
+ return;
+ }
+ }
+
+ JS::Rooted<JSString*> message(cx);
+ {
+ JS::Rooted<JS::Value> msgVal(cx);
+ if (!xpc::NonVoidStringToJsval(cx, aMessage, &msgVal)) {
+ return;
+ }
+ message = msgVal.toString();
+ }
+
+ JS::Rooted<JS::Value> err(cx);
+ if (!JS::CreateError(cx, JSEXN_ERR, stack, fileName, line,
+ JS::ColumnNumberOneOrigin(column.oneOriginValue()),
+ nullptr, message, JS::NothingHandleValue, &err)) {
+ return;
+ }
+
+ MOZ_ASSERT(err.isObject());
+ retVal = &err.toObject();
+ }
+
+ if (aStack && !JS_WrapObject(cx, &retVal)) {
+ return;
+ }
+
+ cleanup.release();
+ aRetVal.set(retVal);
+}
+
+/* static */
+already_AddRefed<Promise> ChromeUtils::RequestIOActivity(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false));
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+ mozilla::net::IOActivityMonitor::RequestActivities(domPromise);
+ return domPromise.forget();
+}
+
+/* static */
+bool ChromeUtils::HasReportingHeaderForOrigin(GlobalObject& global,
+ const nsAString& aOrigin,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ return ReportingHeader::HasReportingHeaderForOrigin(
+ NS_ConvertUTF16toUTF8(aOrigin));
+}
+
+/* static */
+PopupBlockerState ChromeUtils::GetPopupControlState(GlobalObject& aGlobal) {
+ switch (PopupBlocker::GetPopupControlState()) {
+ case PopupBlocker::PopupControlState::openAllowed:
+ return PopupBlockerState::OpenAllowed;
+
+ case PopupBlocker::PopupControlState::openControlled:
+ return PopupBlockerState::OpenControlled;
+
+ case PopupBlocker::PopupControlState::openBlocked:
+ return PopupBlockerState::OpenBlocked;
+
+ case PopupBlocker::PopupControlState::openAbused:
+ return PopupBlockerState::OpenAbused;
+
+ case PopupBlocker::PopupControlState::openOverridden:
+ return PopupBlockerState::OpenOverridden;
+
+ default:
+ MOZ_CRASH(
+ "PopupBlocker::PopupControlState and PopupBlockerState are out of "
+ "sync");
+ }
+}
+
+/* static */
+double ChromeUtils::LastExternalProtocolIframeAllowed(GlobalObject& aGlobal) {
+ TimeStamp when = PopupBlocker::WhenLastExternalProtocolIframeAllowed();
+ if (when.IsNull()) {
+ return 0;
+ }
+
+ TimeDuration duration = TimeStamp::Now() - when;
+ return duration.ToMilliseconds();
+}
+
+/* static */
+void ChromeUtils::ResetLastExternalProtocolIframeAllowed(
+ GlobalObject& aGlobal) {
+ PopupBlocker::ResetLastExternalProtocolIframeAllowed();
+}
+
+/* static */
+void ChromeUtils::EndWheelTransaction(GlobalObject& aGlobal) {
+ // This allows us to end the current wheel transaction from the browser
+ // chrome. We do not need to perform any checks before calling
+ // EndTransaction(), as it should do nothing in the case that there is
+ // no current wheel transaction.
+ WheelTransaction::EndTransaction();
+}
+
+/* static */
+void ChromeUtils::RegisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const WindowActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->RegisterWindowActor(aName, aOptions, aRv);
+}
+
+/* static */
+void ChromeUtils::UnregisterWindowActor(const GlobalObject& aGlobal,
+ const nsACString& aName) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->UnregisterWindowActor(aName);
+}
+
+/* static */
+void ChromeUtils::RegisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->RegisterProcessActor(aName, aOptions, aRv);
+}
+
+/* static */
+void ChromeUtils::UnregisterProcessActor(const GlobalObject& aGlobal,
+ const nsACString& aName) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSActorService> service = JSActorService::GetSingleton();
+ service->UnregisterProcessActor(aName);
+}
+
+/* static */
+bool ChromeUtils::IsClassifierBlockingErrorCode(GlobalObject& aGlobal,
+ uint32_t aError) {
+ return net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
+ static_cast<nsresult>(aError));
+}
+
+/* static */
+void ChromeUtils::PrivateNoteIntentionalCrash(const GlobalObject& aGlobal,
+ ErrorResult& aError) {
+ if (XRE_IsContentProcess()) {
+ NoteIntentionalCrash("tab");
+ return;
+ }
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+/* static */
+nsIDOMProcessChild* ChromeUtils::GetDomProcessChild(const GlobalObject&) {
+ return nsIDOMProcessChild::GetSingleton();
+}
+
+/* static */
+void ChromeUtils::GetAllDOMProcesses(
+ GlobalObject& aGlobal, nsTArray<RefPtr<nsIDOMProcessParent>>& aParents,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.ThrowNotAllowedError(
+ "getAllDOMProcesses() may only be called in the parent process");
+ return;
+ }
+ aParents.Clear();
+ // Always add the parent process nsIDOMProcessParent first
+ aParents.AppendElement(InProcessParent::Singleton());
+
+ // Before adding nsIDOMProcessParent for all the content processes
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ aParents.AppendElement(cp);
+ }
+}
+
+/* static */
+void ChromeUtils::ConsumeInteractionData(
+ GlobalObject& aGlobal, Record<nsString, InteractionData>& aInteractions,
+ ErrorResult& aRv) {
+ if (!XRE_IsParentProcess()) {
+ aRv.ThrowNotAllowedError(
+ "consumeInteractionData() may only be called in the parent "
+ "process");
+ return;
+ }
+ EventStateManager::ConsumeInteractionData(aInteractions);
+}
+
+already_AddRefed<Promise> ChromeUtils::CollectScrollingData(
+ GlobalObject& aGlobal, ErrorResult& aRv) {
+ // Creating a JS promise
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<ScrollingMetrics::ScrollingMetricsPromise> extPromise =
+ ScrollingMetrics::CollectScrollingMetrics();
+
+ extPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](const std::tuple<uint32_t, uint32_t>& aResult) {
+ InteractionData out = {};
+ out.mInteractionTimeInMilliseconds = std::get<0>(aResult);
+ out.mScrollingDistanceInPixels = std::get<1>(aResult);
+ promise->MaybeResolve(out);
+ },
+ [promise](bool aValue) { promise->MaybeReject(NS_ERROR_FAILURE); });
+
+ return promise.forget();
+}
+
+/* static */
+void ChromeUtils::GetFormAutofillConfidences(
+ GlobalObject& aGlobal, const Sequence<OwningNonNull<Element>>& aElements,
+ nsTArray<FormAutofillConfidences>& aResults, ErrorResult& aRv) {
+ FormAutofillNative::GetFormAutofillConfidences(aGlobal, aElements, aResults,
+ aRv);
+}
+
+bool ChromeUtils::IsDarkBackground(GlobalObject&, Element& aElement) {
+ nsIFrame* f = aElement.GetPrimaryFrame(FlushType::Frames);
+ if (!f) {
+ return false;
+ }
+ return nsNativeTheme::IsDarkBackground(f);
+}
+
+double ChromeUtils::DateNow(GlobalObject&) { return JS_Now() / 1000.0; }
+
+/* static */
+void ChromeUtils::EnsureJSOracleStarted(GlobalObject&) {
+ if (StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
+ JSOracleParent::WithJSOracle([](JSOracleParent* aParent) {});
+ }
+}
+
+/* static */
+unsigned ChromeUtils::AliveUtilityProcesses(const GlobalObject&) {
+ const auto& utilityProcessManager =
+ mozilla::ipc::UtilityProcessManager::GetIfExists();
+ return utilityProcessManager ? utilityProcessManager->AliveProcesses() : 0;
+}
+
+/* static */
+void ChromeUtils::GetAllPossibleUtilityActorNames(GlobalObject& aGlobal,
+ nsTArray<nsCString>& aNames) {
+ aNames.Clear();
+ for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) {
+ auto idlName = static_cast<UtilityActorName>(i);
+ aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName));
+ }
+}
+
+/* static */
+bool ChromeUtils::ShouldResistFingerprinting(
+ GlobalObject& aGlobal, JSRFPTarget aTarget,
+ const Nullable<uint64_t>& aOverriddenFingerprintingSettings) {
+ RFPTarget target;
+ switch (aTarget) {
+ case JSRFPTarget::RoundWindowSize:
+ target = RFPTarget::RoundWindowSize;
+ break;
+ case JSRFPTarget::SiteSpecificZoom:
+ target = RFPTarget::SiteSpecificZoom;
+ break;
+ default:
+ MOZ_CRASH("Unhandled JSRFPTarget enum value");
+ }
+
+ bool isPBM = false;
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (global) {
+ nsPIDOMWindowInner* win = global->GetAsInnerWindow();
+ if (win) {
+ nsIDocShell* docshell = win->GetDocShell();
+ if (docshell) {
+ nsDocShell::Cast(docshell)->GetUsePrivateBrowsing(&isPBM);
+ }
+ }
+ }
+
+ Maybe<RFPTarget> overriddenFingerprintingSettings;
+ if (!aOverriddenFingerprintingSettings.IsNull()) {
+ overriddenFingerprintingSettings.emplace(
+ RFPTarget(aOverriddenFingerprintingSettings.Value()));
+ }
+
+ // This global object appears to be the global window, not for individual
+ // sites so to exempt individual sites (instead of just PBM/Not-PBM windows)
+ // more work would be needed to get the correct context.
+ return nsRFPService::IsRFPEnabledFor(isPBM, target,
+ overriddenFingerprintingSettings);
+}
+
+std::atomic<uint32_t> ChromeUtils::sDevToolsOpenedCount = 0;
+
+/* static */
+bool ChromeUtils::IsDevToolsOpened() {
+ return ChromeUtils::sDevToolsOpenedCount > 0;
+}
+
+/* static */
+bool ChromeUtils::IsDevToolsOpened(GlobalObject& aGlobal) {
+ return ChromeUtils::IsDevToolsOpened();
+}
+
+/* static */
+void ChromeUtils::NotifyDevToolsOpened(GlobalObject& aGlobal) {
+ ChromeUtils::sDevToolsOpenedCount++;
+}
+
+/* static */
+void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
+ MOZ_ASSERT(ChromeUtils::sDevToolsOpenedCount >= 1);
+ ChromeUtils::sDevToolsOpenedCount--;
+}
+
+#ifdef MOZ_WMF_CDM
+/* static */
+already_AddRefed<Promise> ChromeUtils::GetWMFContentDecryptionModuleInformation(
+ GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+ MFCDMService::GetAllKeySystemsCapabilities(domPromise);
+ return domPromise.forget();
+}
+#endif
+
+already_AddRefed<Promise> ChromeUtils::GetGMPContentDecryptionModuleInformation(
+ GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<Promise> domPromise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(domPromise);
+ KeySystemConfig::GetGMPKeySystemConfigs(domPromise);
+ return domPromise.forget();
+}
+
+} // namespace mozilla::dom