/* -*- 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 "js/CharacterEncoding.h" #include "js/Object.h" // JS::GetClass #include "js/SavedFrameAPI.h" #include "jsfriendapi.h" #include "WrapperFactory.h" #include "mozilla/Base64.h" #include "mozilla/CycleCollectedJSRuntime.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/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/ReportingHeader.h" #include "mozilla/dom/UnionTypes.h" #include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/ipc/GeckoChildProcessHost.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "IOActivityMonitor.h" #include "nsThreadUtils.h" #include "mozJSComponentLoader.h" #include "GeckoProfiler.h" #include "nsIException.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) { #ifdef MOZ_GECKO_PROFILER MarkerOptions options; MarkerCategory category = ::geckoprofiler::category::JS; DOMHighResTimeStamp startTime = 0; if (aOptions.IsDouble()) { startTime = aOptions.GetAsDouble(); } else { const ProfilerMarkerOptions& opt = aOptions.GetAsProfilerMarkerOptions(); startTime = opt.mStartTime; if (opt.mCaptureStack) { 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))); } } { 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)); } } #endif // MOZ_GECKO_PROFILER } /* static */ void ChromeUtils::WaiveXrays(GlobalObject& aGlobal, JS::HandleValue aVal, JS::MutableHandleValue aRetval, ErrorResult& aRv) { JS::RootedValue 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::HandleValue aVal, JS::MutableHandleValue aRetval, ErrorResult& aRv) { if (!aVal.isObject()) { aRetval.set(aVal); return; } JS::RootedObject 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::HandleObject aObj, bool aUnwrap, nsAString& aRetval) { JS::RootedObject obj(aGlobal.Context(), aObj); if (aUnwrap) { obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); } aRetval = NS_ConvertUTF8toUTF16(nsDependentCString(JS::GetClass(obj)->name)); } /* static */ void ChromeUtils::ShallowClone(GlobalObject& aGlobal, JS::HandleObject aObj, JS::HandleObject aTarget, JS::MutableHandleObject 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::RootedObject 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::RootedId id(cx); for (jsid idVal : ids) { id = idVal; if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc)) { continue; } if (desc.setter() || desc.getter()) { continue; } valuesIds.infallibleAppend(id); values.infallibleAppend(desc.value()); } } JS::RootedObject 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::RootedObject target(cx, js::CheckedUnwrapDynamic(aTarget, cx)); if (!target) { js::ReportAccessDenied(cx); return; } ar.emplace(cx, target); } obj = JS_NewPlainObject(cx); if (!obj) { return; } JS::RootedValue value(cx); JS::RootedId 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 nsAString& aResourceURI, const Optional>& aTargetObj, JS::MutableHandle aRetval, ErrorResult& aRv) { RefPtr moduleloader = mozJSComponentLoader::Get(); MOZ_ASSERT(moduleloader); NS_ConvertUTF16toUTF8 registryLocation(aResourceURI); AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("ChromeUtils::Import", OTHER, registryLocation); JSContext* cx = aGlobal.Context(); bool ignoreExports = aTargetObj.WasPassed() && !aTargetObj.Value(); JS::RootedObject global(cx); JS::RootedObject exports(cx); nsresult rv = moduleloader->Import(cx, registryLocation, &global, &exports, ignoreExports); 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 (ignoreExports) { // Since we're ignoring exported symbols, return the module global rather // than an exports object. // // Note: This behavior is deprecated, since it is incompatible with ES6 // module semantics, which don't include any such global object. if (!JS_WrapObject(cx, &global)) { aRv.Throw(NS_ERROR_FAILURE); return; } aRetval.set(global); 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); } namespace module_getter { static const size_t SLOT_ID = 0; static const size_t SLOT_URI = 1; 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 ModuleGetter(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; } 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 = mozJSComponentLoader::Get(); MOZ_ASSERT(moduleloader); 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; } JS::RootedValue value(aCx); if (!JS_GetPropertyById(aCx, moduleExports, id, &value)) { return false; } if (!JS_DefinePropertyById(aCx, thisObj, id, value, JSPROP_ENUMERATE)) { return false; } args.rval().set(value); return true; } static bool ModuleSetter(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 DefineGetter(JSContext* aCx, JS::Handle aTarget, const nsAString& aId, const nsAString& aResourceURI) { JS::RootedValue uri(aCx); JS::RootedValue 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, ModuleGetter, 0, 0, id))); JS::Rooted setter( aCx, JS_GetFunctionObject( js::NewFunctionByIdWithReserved(aCx, ModuleSetter, 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_GETTER | JSPROP_SETTER | JSPROP_ENUMERATE); } } // namespace module_getter /* static */ void ChromeUtils::DefineModuleGetter(const GlobalObject& global, JS::Handle target, const nsAString& id, const nsAString& resourceURI, ErrorResult& aRv) { if (!module_getter::DefineGetter(global.Context(), target, id, resourceURI)) { aRv.NoteJSContextException(global.Context()); } } /* 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::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; } #ifdef NIGHTLY_BUILD /* static */ void ChromeUtils::GetRecentJSDevError(GlobalObject& aGlobal, JS::MutableHandleValue 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::ClearStyleSheetCache(GlobalObject&, nsIPrincipal* aForPrincipal) { SharedStyleSheetCache::Clear(aForPrincipal); } #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"); 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(WebLargeAllocation, WebLargeAllocation); PROCTYPE_TO_WEBIDL_CASE(Browser, Browser); PROCTYPE_TO_WEBIDL_CASE(Plugin, Plugin); PROCTYPE_TO_WEBIDL_CASE(IPDLUnitTest, IpdlUnitTest); PROCTYPE_TO_WEBIDL_CASE(GMPlugin, GmpPlugin); PROCTYPE_TO_WEBIDL_CASE(GPU, Gpu); PROCTYPE_TO_WEBIDL_CASE(VR, Vr); PROCTYPE_TO_WEBIDL_CASE(RDD, Rdd); PROCTYPE_TO_WEBIDL_CASE(Socket, Socket); PROCTYPE_TO_WEBIDL_CASE(RemoteSandboxBroker, RemoteSandboxBroker); #ifdef MOZ_ENABLE_FORKSERVER PROCTYPE_TO_WEBIDL_CASE(ForkServer, ForkServer); #endif 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()); // First handle non-ContentParent processes. mozilla::ipc::GeckoChildProcessHost::GetAll( [&requests](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) { auto handle = aGeckoProcess->GetChildProcessHandle(); if (!handle) { // Something went wrong with this process, it may be dead already, // fail gracefully. return; } base::ProcessId childPid = base::GetProcId(handle); mozilla::ProcType type = mozilla::ProcType::Unknown; switch (aGeckoProcess->GetProcessType()) { case GeckoProcessType::GeckoProcessType_Content: { // These processes are handled separately. return; } case GeckoProcessType::GeckoProcessType_Default: type = mozilla::ProcType::Browser; break; case GeckoProcessType::GeckoProcessType_Plugin: type = mozilla::ProcType::Plugin; break; case GeckoProcessType::GeckoProcessType_GMPlugin: type = mozilla::ProcType::GMPlugin; break; case GeckoProcessType::GeckoProcessType_GPU: type = mozilla::ProcType::GPU; break; case GeckoProcessType::GeckoProcessType_VR: type = mozilla::ProcType::VR; break; case GeckoProcessType::GeckoProcessType_RDD: type = mozilla::ProcType::RDD; break; case GeckoProcessType::GeckoProcessType_Socket: type = mozilla::ProcType::Socket; break; case GeckoProcessType::GeckoProcessType_RemoteSandboxBroker: type = mozilla::ProcType::RemoteSandboxBroker; break; #ifdef MOZ_ENABLE_FORKSERVER case GeckoProcessType::GeckoProcessType_ForkServer: type = mozilla::ProcType::ForkServer; break; #endif default: // Leave the default Unknown value in |type|. break; } requests.EmplaceBack( /* aPid = */ childPid, /* aProcessType = */ type, /* aOrigin = */ ""_ns, /* aWindowInfo = */ nsTArray(), // Without a // ContentProcess, no // DOM windows. /* 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; } auto handle = contentParent->Process()->GetChildProcessHandle(); if (!handle) { // 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, 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 == LARGE_ALLOCATION_REMOTE_TYPE) { type = mozilla::ProcType::WebLargeAllocation; } 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& browserParentWrapper : contentParent->ManagedPBrowserParent()) { for (const auto& windowGlobalParentWrapper : browserParentWrapper.GetKey()->ManagedPWindowGlobalParent()) { // WindowGlobalParent is the only immediate subclass of // PWindowGlobalParent. auto* windowGlobalParent = static_cast( windowGlobalParentWrapper.GetKey()); 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 = */ base::GetProcId(handle), /* aProcessType = */ type, /* aOrigin = */ origin, /* aWindowInfo = */ std::move(windows), /* 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; } } } // 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 */ 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::RootedObject 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::RootedObject retVal(cx); { JS::RootedString fileName(cx, JS_GetEmptyString(cx)); uint32_t line = 0; uint32_t column = 0; Maybe ar; JS::RootedObject 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::RootedString message(cx); { JS::RootedValue 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, &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::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); } } } // namespace mozilla::dom