gecko-dev/dom/base/ChromeUtils.cpp

1862 строки
62 KiB
C++

/* -*- 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 "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, &paramsVal.toObject());
JS::Rooted<JS::Value> targetVal(aCx);
JS::Rooted<JS::Value> lambdaVal(aCx);
if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_TARGET, &targetVal)) {
return false;
}
if (!JS_GetElement(aCx, paramsObj, PARAM_INDEX_LAMBDA, &lambdaVal)) {
return false;
}
JS::Rooted<JSObject*> targetObj(aCx, &targetVal.toObject());
JS::Rooted<JS::Value> value(aCx);
if (!JS::Call(aCx, targetObj, lambdaVal, JS::HandleValueArray::empty(),
&value)) {
return false;
}
if (!JS_DefinePropertyById(aCx, targetObj, id, value, JSPROP_ENUMERATE)) {
return false;
}
args.rval().set(value);
return true;
}
static bool DefineLazyGetter(JSContext* aCx, JS::Handle<JSObject*> aTarget,
JS::Handle<JS::Value> aName,
JS::Handle<JSObject*> aLambda) {
JS::Rooted<jsid> id(aCx);
if (!JS_ValueToId(aCx, aName, &id)) {
return false;
}
JS::Rooted<JSObject*> getter(
aCx, JS_GetFunctionObject(
js::NewFunctionByIdWithReserved(aCx, JSLazyGetter, 0, 0, id)));
if (!getter) {
JS_ReportOutOfMemory(aCx);
return false;
}
JS::RootedVector<JS::Value> params(aCx);
if (!params.resize(PARAMS_COUNT)) {
return false;
}
params[PARAM_INDEX_TARGET].setObject(*aTarget);
params[PARAM_INDEX_LAMBDA].setObject(*aLambda);
JS::Rooted<JSObject*> paramsObj(aCx, JS::NewArrayObject(aCx, params));
if (!paramsObj) {
return false;
}
js::SetFunctionNativeReserved(getter, SLOT_ID, aName);
js::SetFunctionNativeReserved(getter, SLOT_PARAMS,
JS::ObjectValue(*paramsObj));
return JS_DefinePropertyById(aCx, aTarget, id, getter, nullptr,
JSPROP_ENUMERATE);
}
enum class ModuleType { JSM, ESM };
static bool ModuleGetterImpl(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
ModuleType aType) {
JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
JS::Rooted<JSObject*> callee(aCx);
JS::Rooted<JSObject*> thisObj(aCx);
JS::Rooted<jsid> id(aCx);
if (!ExtractArgs(aCx, args, &callee, &thisObj, &id)) {
return false;
}
JS::Rooted<JSString*> moduleURI(
aCx, js::GetFunctionNativeReserved(callee, SLOT_URI).toString());
JS::UniqueChars bytes = JS_EncodeStringToUTF8(aCx, moduleURI);
if (!bytes) {
return false;
}
nsDependentCString uri(bytes.get());
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::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));
}
}
} // namespace mozilla::dom