/* -*- 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 aMap, JS::MutableHandle aRetval, ErrorResult& aRv) { if (!aMap.isObject()) { aRetval.setUndefined(); } else { JSContext* cx = aGlobal.Context(); JS::Rooted objRet(cx); JS::Rooted 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 aSet, JS::MutableHandle aRetval, ErrorResult& aRv) { if (!aSet.isObject()) { aRetval.setUndefined(); } else { JSContext* cx = aGlobal.Context(); JS::Rooted objRet(cx); JS::Rooted 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 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 data; nsresult rv = mozilla::Base64URLDecode(aString, paddingPolicy, data); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } JS::Rooted 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 location = GetCurrentJSStack(1)) { location->GetFilename(aGlobal.Context(), filename); lineNo = location->GetLineNumber(aGlobal.Context()); } else { filename.Assign(u""_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& 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; if (NS_IsMainThread()) { nsCOMPtr 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 aVal, JS::MutableHandle aRetval, ErrorResult& aRv) { JS::Rooted 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 aVal, JS::MutableHandle aRetval, ErrorResult& aRv) { if (!aVal.isObject()) { aRetval.set(aVal); return; } JS::Rooted 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 aObj, bool aUnwrap, nsAString& aRetval) { JS::Rooted 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 aObj, bool aUnwrap) { JS::Rooted 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 aObj, JS::Handle aTarget, JS::MutableHandle aRetval, ErrorResult& aRv) { JSContext* cx = aGlobal.Context(); auto cleanup = MakeScopeExit([&]() { aRv.NoteJSContextException(cx); }); JS::Rooted ids(cx, JS::IdVector(cx)); JS::RootedVector values(cx); JS::RootedVector 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 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> desc(cx); JS::Rooted 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 obj(cx); { Maybe 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 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 value(cx); JS::Rooted 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 = new IdleDeadline(mParent, mTimedOut, deadline.ToMilliseconds()); RefPtr 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 mCallback; nsCOMPtr mParent; nsCOMPtr 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 global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); auto runnable = MakeRefPtr(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>& aTargetObj, JS::MutableHandle 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 global(cx); JS::Rooted 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& 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 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 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 aCallee, JS::MutableHandle aThisObj, JS::MutableHandle aId) { aCallee.set(&aArgs.callee()); JS::Handle thisv = aArgs.thisv(); if (!thisv.isObject()) { JS_ReportErrorASCII(aCx, "Invalid target object"); return false; } aThisObj.set(&thisv.toObject()); JS::Rooted 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 callee(aCx); JS::Rooted unused(aCx); JS::Rooted id(aCx); if (!ExtractArgs(aCx, args, &callee, &unused, &id)) { return false; } JS::Rooted 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 paramsObj(aCx, ¶msVal.toObject()); JS::Rooted targetVal(aCx); JS::Rooted 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 targetObj(aCx, &targetVal.toObject()); JS::Rooted 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 aTarget, JS::Handle aName, JS::Handle aLambda) { JS::Rooted id(aCx); if (!JS_ValueToId(aCx, aName, &id)) { return false; } JS::Rooted getter( aCx, JS_GetFunctionObject( js::NewFunctionByIdWithReserved(aCx, JSLazyGetter, 0, 0, id))); if (!getter) { JS_ReportOutOfMemory(aCx); return false; } JS::RootedVector params(aCx); if (!params.resize(PARAMS_COUNT)) { return false; } params[PARAM_INDEX_TARGET].setObject(*aTarget); params[PARAM_INDEX_LAMBDA].setObject(*aLambda); JS::Rooted 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 callee(aCx); JS::Rooted thisObj(aCx); JS::Rooted id(aCx); if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) { return false; } JS::Rooted 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(), JS::GetNonCCWObjectGlobal(js::UncheckedUnwrap(thisObj))); MOZ_ASSERT(moduleloader); JS::Rooted value(aCx); if (aType == ModuleType::JSM) { JS::Rooted moduleGlobal(aCx); JS::Rooted 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 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 callee(aCx); JS::Rooted thisObj(aCx); JS::Rooted 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 aTarget, const nsAString& aId, const nsAString& aResourceURI) { JS::Rooted uri(aCx); JS::Rooted idValue(aCx); JS::Rooted 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 getter( aCx, JS_GetFunctionObject( js::NewFunctionByIdWithReserved(aCx, JSModuleGetter, 0, 0, id))); JS::Rooted 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 aTarget, JS::Handle aId, JS::Handle aResourceURI) { JS::Rooted idVal(aCx, JS::StringValue(aId.toString())); JS::Rooted getter( aCx, JS_GetFunctionObject(js::NewFunctionByIdWithReserved( aCx, ESModuleGetter, 0, 0, aId))); JS::Rooted 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 aTarget, JS::Handle aName, JS::Handle 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 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 target, JS::Handle modules, ErrorResult& aRv) { JSContext* cx = global.Context(); JS::Rooted props(cx, JS::IdVector(cx)); if (!JS_Enumerate(cx, modules, &props)) { aRv.NoteJSContextException(cx); return; } JS::Rooted prop(cx); JS::Rooted 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 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 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(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 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 global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); RefPtr 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 contentParents; ContentParent::GetAll(contentParents); // Prepare our background request. // We reserve one more slot for the browser process itself. nsTArray requests(contentParents.Length() + 1); // Requesting process info for the browser process itself. requests.EmplaceBack( /* aPid = */ base::GetCurrentProcId(), /* aProcessType = */ ProcType::Browser, /* aOrigin = */ ""_ns, /* aWindowInfo = */ nsTArray(), /* aUtilityInfo = */ nsTArray()); // 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 utilityActors; if (aGeckoProcess->GetProcessType() == GeckoProcessType::GeckoProcessType_Utility) { RefPtr 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(), // 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 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(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(), /* aChild = */ contentParent->ChildID() #ifdef XP_MACOSX , /* aChildTask = */ contentParent->Process()->GetChildTask() #endif // XP_MACOSX ); } // Now place background request. RefPtr target = global->EventTargetFor(TaskCategory::Performance); mozilla::GetProcInfo(std::move(requests)) ->Then( target, __func__, [target, domPromise](const HashMap& 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 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 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 ChromeUtils::RequestPerformanceMetrics( GlobalObject& aGlobal, ErrorResult& aRv) { MOZ_ASSERT(XRE_IsParentProcess()); // Creating a JS promise nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); RefPtr domPromise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } MOZ_ASSERT(domPromise); RefPtr target = global->EventTargetFor(TaskCategory::Performance); // requesting metrics, that will be returned into the promise PerformanceMetricsCollector::RequestMetrics()->Then( target, __func__, [domPromise, target](nsTArray&& 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(aMask)); } already_AddRefed ChromeUtils::CollectPerfStats(GlobalObject& aGlobal, ErrorResult& aRv) { // Creating a JS promise nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); RefPtr promise = Promise::Create(global, aRv); if (aRv.Failed()) { return nullptr; } RefPtr 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 aRetval) { JSContext* cx = aGlobal.Context(); auto* principals = nsJSPrincipals::get(aPrincipal); JS::StackCapture captureMode(JS::FirstSubsumedFrame(cx, principals)); JS::Rooted 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 aStack, JS::MutableHandle 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 retVal(cx); { JS::Rooted fileName(cx, JS_GetEmptyString(cx)); uint32_t line = 0; uint32_t column = 0; Maybe ar; JS::Rooted 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 message(cx); { JS::Rooted msgVal(cx); if (!xpc::NonVoidStringToJsval(cx, aMessage, &msgVal)) { return; } message = msgVal.toString(); } JS::Rooted 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 ChromeUtils::RequestIOActivity(GlobalObject& aGlobal, ErrorResult& aRv) { MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false)); nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); RefPtr 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 service = JSActorService::GetSingleton(); service->RegisterWindowActor(aName, aOptions, aRv); } /* static */ void ChromeUtils::UnregisterWindowActor(const GlobalObject& aGlobal, const nsACString& aName) { MOZ_ASSERT(XRE_IsParentProcess()); RefPtr 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 service = JSActorService::GetSingleton(); service->RegisterProcessActor(aName, aOptions, aRv); } /* static */ void ChromeUtils::UnregisterProcessActor(const GlobalObject& aGlobal, const nsACString& aName) { MOZ_ASSERT(XRE_IsParentProcess()); RefPtr service = JSActorService::GetSingleton(); service->UnregisterProcessActor(aName); } /* static */ bool ChromeUtils::IsClassifierBlockingErrorCode(GlobalObject& aGlobal, uint32_t aError) { return net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( static_cast(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>& 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& aInteractions, ErrorResult& aRv) { if (!XRE_IsParentProcess()) { aRv.ThrowNotAllowedError( "consumeInteractionData() may only be called in the parent " "process"); return; } EventStateManager::ConsumeInteractionData(aInteractions); } already_AddRefed ChromeUtils::CollectScrollingData( GlobalObject& aGlobal, ErrorResult& aRv) { // Creating a JS promise nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); RefPtr promise = Promise::Create(global, aRv); if (aRv.Failed()) { return nullptr; } RefPtr extPromise = ScrollingMetrics::CollectScrollingMetrics(); extPromise->Then( GetCurrentSerialEventTarget(), __func__, [promise](const std::tuple& 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>& aElements, nsTArray& 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& aNames) { aNames.Clear(); for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) { auto idlName = static_cast(i); aNames.AppendElement(WebIDLUtilityActorNameValues::GetString(idlName)); } } std::atomic 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