diff options
Diffstat (limited to 'dom/base/ChromeUtils.cpp')
-rw-r--r-- | dom/base/ChromeUtils.cpp | 1895 |
1 files changed, 1895 insertions, 0 deletions
diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp new file mode 100644 index 0000000000..8804b73e39 --- /dev/null +++ b/dom/base/ChromeUtils.cpp @@ -0,0 +1,1895 @@ +/* -*- 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/CharacterEncoding.h" +#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/PerformanceMetricsCollector.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/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/WheelHandlingHelper.h" +#include "IOActivityMonitor.h" +#include "nsNativeTheme.h" +#include "nsThreadUtils.h" +#include "mozJSModuleLoader.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "nsIException.h" +#include "VsyncSource.h" + +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) { + size_t length = 0; + uint8_t* data = nullptr; + if (aSource.IsArrayBuffer()) { + const ArrayBuffer& buffer = aSource.GetAsArrayBuffer(); + buffer.ComputeState(); + length = buffer.Length(); + data = buffer.Data(); + } else if (aSource.IsArrayBufferView()) { + const ArrayBufferView& view = aSource.GetAsArrayBufferView(); + view.ComputeState(); + length = view.Length(); + data = view.Data(); + } else { + MOZ_CRASH("Uninitialized union: expected buffer or view"); + } + + auto paddingPolicy = aOptions.mPad ? Base64URLEncodePaddingPolicy::Include + : Base64URLEncodePaddingPolicy::Omit; + nsresult rv = mozilla::Base64URLEncode(length, data, 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.Length(), data.Elements())); + if (NS_WARN_IF(!buffer)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + 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 */ +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 */ +void ChromeUtils::ImportESModule( + const GlobalObject& aGlobal, const nsAString& aResourceURI, + const ImportESModuleOptionsDictionary& aOptions, + JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) { + RefPtr moduleloader = + GetContextualESLoader(aOptions.mLoadInDevToolsLoader, aGlobal.Get()); + MOZ_ASSERT(moduleloader); + + NS_ConvertUTF16toUTF8 registryLocation(aResourceURI); + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "ChromeUtils::ImportESModule", OTHER, registryLocation); + + JSContext* cx = aGlobal.Context(); + + 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); +} + +namespace lazy_getter { + +static const size_t SLOT_ID = 0; +static const size_t SLOT_URI = 1; +static const size_t SLOT_PARAMS = 1; + +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, ¶msVal.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()); + + RefPtr moduleloader = + aType == ModuleType::JSM + ? mozJSModuleLoader::Get() + : GetContextualESLoader( + Optional<bool>(), + JS::GetNonCCWObjectGlobal(js::UncheckedUnwrap(thisObj))); + MOZ_ASSERT(moduleloader); + + JS::Rooted<JS::Value> value(aCx); + if (aType == ModuleType::JSM) { + 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 { + 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 (!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) { + JS::Rooted<JS::Value> idVal(aCx, JS::StringValue(aId.toString())); + + 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); + + 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, + 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; + } + + 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)) { + aRv.NoteJSContextException(cx); + return; + } + } +} + +/* 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->EventTargetFor(TaskCategory::Performance); + 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(); +} + +/* static */ +already_AddRefed<Promise> ChromeUtils::RequestPerformanceMetrics( + GlobalObject& aGlobal, ErrorResult& aRv) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Creating a JS promise + 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); + RefPtr<nsISerialEventTarget> target = + global->EventTargetFor(TaskCategory::Performance); + + // requesting metrics, that will be returned into the promise + PerformanceMetricsCollector::RequestMetrics()->Then( + target, __func__, + [domPromise, + target](nsTArray<dom::PerformanceInfoDictionary>&& aResults) { + domPromise->MaybeResolve(std::move(aResults)); + }, + [domPromise](const nsresult& aRv) { domPromise->MaybeReject(aRv); }); + + // sending back the promise instance + return domPromise.forget(); +} + +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; + uint32_t column = 0; + + 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, column, 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)); + } +} + +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--; +} + +} // namespace mozilla::dom |