mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-11 21:58:41 +02:00
Promise::Compartment is unused. The callers that want to call AutoJSAPI::Init can pass it an nsIGlobalObject, which is actually _more_ efficient, since passing a JSObject just gets an nsIGlobalObject from it and passes that. Differential Revision: https://phabricator.services.mozilla.com/D29703 --HG-- extra : moz-landing-system : lando
407 lines
13 KiB
C++
407 lines
13 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 "UntrustedModules.h"
|
|
|
|
#include "core/TelemetryCommon.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/WinDllServices.h"
|
|
#include "nsLocalFile.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsXPCOMCIDInternal.h"
|
|
|
|
namespace mozilla {
|
|
namespace Telemetry {
|
|
|
|
static const int32_t kUntrustedModuleLoadEventsTelemetryVersion = 1;
|
|
|
|
/**
|
|
* Limits the length of a string by removing the middle of the string, replacing
|
|
* with ellipses.
|
|
* e.g. LimitStringLength("hello world", 6) would result in "he...d"
|
|
*
|
|
* @param aStr [in,out] The string to transform
|
|
* @param aMaxFieldLength [in] The maximum length of the resulting string.
|
|
* this must be long enough to hold the ellipses.
|
|
*/
|
|
static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
|
|
if (aStr.Length() <= aMaxFieldLength) {
|
|
return;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_STRING(kEllipses, "...");
|
|
|
|
MOZ_ASSERT(aMaxFieldLength >= kEllipses.Length());
|
|
size_t cutPos = (aMaxFieldLength - kEllipses.Length()) / 2;
|
|
size_t rightLen = aMaxFieldLength - kEllipses.Length() - cutPos;
|
|
size_t cutLen = aStr.Length() - (cutPos + rightLen);
|
|
|
|
aStr.Replace(cutPos, cutLen, kEllipses);
|
|
}
|
|
|
|
/**
|
|
* Adds a string property to a JS object, that's limited in length using
|
|
* LimitStringLength().
|
|
*
|
|
* @param cx [in] The JS context
|
|
* @param aObj [in] The object to add the property to
|
|
* @param aName [in] The name of the property to add
|
|
* @param aVal [in] The JS value of the resulting property.
|
|
* @param aMaxFieldLength [in] The maximum length of the value
|
|
* (see LimitStringLength())
|
|
* @return true upon success
|
|
*/
|
|
static bool AddLengthLimitedStringProp(JSContext* cx, JS::HandleObject aObj,
|
|
const char* aName, const nsAString& aVal,
|
|
size_t aMaxFieldLength = 260) {
|
|
JS::RootedValue jsval(cx);
|
|
nsAutoString shortVal(aVal);
|
|
LimitStringLength(shortVal, aMaxFieldLength);
|
|
jsval.setString(Common::ToJSString(cx, shortVal));
|
|
return JS_DefineProperty(cx, aObj, aName, jsval, JSPROP_ENUMERATE);
|
|
};
|
|
|
|
/**
|
|
* Convert the given mozilla::Vector to a JavaScript array.
|
|
*
|
|
* @param cx [in] The JS context.
|
|
* @param aRet [out] This gets assigned to the newly created
|
|
* array object.
|
|
* @param aContainer [in] The source array to convert.
|
|
* @param aElementConverter [in] A callable used to convert each array element
|
|
* to a JS element. The form of this function is:
|
|
* bool(JSContext *cx,
|
|
* JS::MutableHandleValue aRet,
|
|
* const ArrayElementT& aElement)
|
|
* @return true if aRet was successfully assigned to the new array object.
|
|
*/
|
|
template <typename T, size_t N, typename AllocPolicy, typename Converter>
|
|
static bool VectorToJSArray(JSContext* cx, JS::MutableHandleObject aRet,
|
|
const Vector<T, N, AllocPolicy>& aContainer,
|
|
Converter&& aElementConverter) {
|
|
JS::RootedObject arr(cx, JS_NewArrayObject(cx, 0));
|
|
if (!arr) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < aContainer.length(); ++i) {
|
|
JS::RootedValue jsel(cx);
|
|
if (!aElementConverter(cx, &jsel, aContainer[i])) {
|
|
return false;
|
|
}
|
|
if (!JS_DefineElement(cx, arr, i, jsel, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
aRet.set(arr);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts a ModuleLoadEvent::ModuleInfo to a JS object.
|
|
*
|
|
* @param cx [in] The JS context.
|
|
* @param aRet [out] This gets assigned to the newly created object.
|
|
* @param aModInfo [in] The source object to convert.
|
|
* @return true if aRet was successfully assigned.
|
|
*/
|
|
static bool ModuleInfoToJSObj(JSContext* cx, JS::MutableHandleObject aRet,
|
|
const ModuleLoadEvent::ModuleInfo& aModInfo) {
|
|
JS::RootedObject modObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!modObj) {
|
|
return false;
|
|
}
|
|
|
|
JS::RootedValue jsval(cx);
|
|
|
|
nsPrintfCString strBaseAddress("0x%p", (void*)aModInfo.mBase);
|
|
jsval.setString(Common::ToJSString(cx, strBaseAddress));
|
|
if (!JS_DefineProperty(cx, modObj, "baseAddress", jsval, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
jsval.setString(Common::ToJSString(cx, aModInfo.mFileVersion));
|
|
if (!JS_DefineProperty(cx, modObj, "fileVersion", jsval, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
if (!AddLengthLimitedStringProp(cx, modObj, "loaderName",
|
|
aModInfo.mLdrName)) {
|
|
return false;
|
|
}
|
|
|
|
if (!AddLengthLimitedStringProp(cx, modObj, "moduleName",
|
|
aModInfo.mFilePathClean)) {
|
|
return false;
|
|
}
|
|
|
|
if (aModInfo.mLoadDurationMS.isSome()) {
|
|
jsval.setNumber(aModInfo.mLoadDurationMS.value());
|
|
if (!JS_DefineProperty(cx, modObj, "loadDurationMS", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
jsval.setNumber((uint32_t)aModInfo.mTrustFlags);
|
|
if (!JS_DefineProperty(cx, modObj, "moduleTrustFlags", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
aRet.set(modObj);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts a ModuleLoadEvent object to a Javascript array
|
|
*
|
|
* @param cx [in] The JS context
|
|
* @param aRet [out] Handle that receives the resulting array object
|
|
* @param aEvent [in] The event to convert from
|
|
* @return true upon success
|
|
*/
|
|
static bool ModuleLoadEventToJSArray(JSContext* cx, JS::MutableHandleValue aRet,
|
|
const ModuleLoadEvent& aEvent) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
JS::RootedValue jsval(cx);
|
|
JS::RootedObject eObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!eObj) {
|
|
return false;
|
|
}
|
|
|
|
jsval.setNumber((uint32_t)aEvent.mThreadID);
|
|
if (!JS_DefineProperty(cx, eObj, "threadID", jsval, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
jsval.setBoolean(aEvent.mIsStartup);
|
|
if (!JS_DefineProperty(cx, eObj, "isStartup", jsval, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
// Javascript doesn't like 64-bit integers; convert to double.
|
|
jsval.setNumber((double)aEvent.mProcessUptimeMS);
|
|
if (!JS_DefineProperty(cx, eObj, "processUptimeMS", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
// This function should always get called on the main thread
|
|
if (::GetCurrentThreadId() == aEvent.mThreadID) {
|
|
jsval.setString(Common::ToJSString(cx, NS_LITERAL_STRING("Main Thread")));
|
|
} else {
|
|
jsval.setString(Common::ToJSString(cx, aEvent.mThreadName));
|
|
}
|
|
if (!JS_DefineProperty(cx, eObj, "threadName", jsval, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
JS::RootedObject modulesArray(cx);
|
|
bool ok = VectorToJSArray(cx, &modulesArray, aEvent.mModules,
|
|
[](JSContext* cx, JS::MutableHandleValue aRet,
|
|
const ModuleLoadEvent::ModuleInfo& aModInfo) {
|
|
JS::RootedObject obj(cx);
|
|
if (!ModuleInfoToJSObj(cx, &obj, aModInfo)) {
|
|
return false;
|
|
}
|
|
aRet.setObject(*obj);
|
|
return true;
|
|
});
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, eObj, "modules", modulesArray, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
|
|
aRet.setObject(*eObj);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Converts a UntrustedModuleLoadTelemetryData to a JS object.
|
|
*
|
|
* @param aData [in] The source object to convert.
|
|
* @param cx [in] The JS context.
|
|
* @param aRet [out] This gets assigned to the newly created object.
|
|
* @return nsresult
|
|
*/
|
|
nsresult GetUntrustedModuleLoadEventsJSValue(
|
|
const UntrustedModuleLoadTelemetryData& aData, JSContext* cx,
|
|
JS::MutableHandle<JS::Value> aRet) {
|
|
if (aData.mEvents.empty()) {
|
|
aRet.setNull();
|
|
return NS_OK;
|
|
}
|
|
|
|
JS::RootedValue jsval(cx);
|
|
JS::RootedObject mainObj(cx, JS_NewObject(cx, nullptr));
|
|
if (!mainObj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
jsval.setNumber((uint32_t)aData.mErrorModules);
|
|
if (!JS_DefineProperty(cx, mainObj, "errorModules", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
jsval.setNumber((uint32_t)kUntrustedModuleLoadEventsTelemetryVersion);
|
|
if (!JS_DefineProperty(cx, mainObj, "structVersion", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aData.mXULLoadDurationMS.isSome()) {
|
|
jsval.setNumber(aData.mXULLoadDurationMS.value());
|
|
if (!JS_DefineProperty(cx, mainObj, "xulLoadDurationMS", jsval,
|
|
JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
JS::RootedObject eventsArray(cx);
|
|
if (!VectorToJSArray(cx, &eventsArray, aData.mEvents,
|
|
&ModuleLoadEventToJSArray)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, mainObj, "events", eventsArray,
|
|
JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
JS::RootedObject combinedStacksObj(cx,
|
|
CreateJSStackObject(cx, aData.mStacks));
|
|
if (!combinedStacksObj) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (!JS_DefineProperty(cx, mainObj, "combinedStacks", combinedStacksObj,
|
|
JSPROP_ENUMERATE)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
aRet.setObject(*mainObj);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class GetUntrustedModulesMainThreadRunnable final : public Runnable {
|
|
nsMainThreadPtrHandle<dom::Promise> mPromise;
|
|
bool mDataOK;
|
|
UntrustedModuleLoadTelemetryData mData;
|
|
nsCOMPtr<nsIThread> mWorkerThread;
|
|
|
|
public:
|
|
GetUntrustedModulesMainThreadRunnable(
|
|
const nsMainThreadPtrHandle<dom::Promise>& aPromise, bool aDataOK,
|
|
UntrustedModuleLoadTelemetryData&& aData)
|
|
: Runnable("GetUntrustedModulesMainThreadRunnable"),
|
|
mPromise(aPromise),
|
|
mDataOK(aDataOK),
|
|
mData(std::move(aData)),
|
|
mWorkerThread(do_GetCurrentThread()) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
}
|
|
|
|
NS_IMETHOD
|
|
Run() override {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mWorkerThread->Shutdown();
|
|
|
|
dom::AutoJSAPI jsapi;
|
|
if (NS_WARN_IF(!jsapi.Init(mPromise->GetGlobalObject()))) {
|
|
mPromise->MaybeReject(NS_ERROR_FAILURE);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mDataOK) {
|
|
mPromise->MaybeReject(NS_ERROR_FAILURE);
|
|
return NS_OK;
|
|
}
|
|
|
|
JSContext* cx = jsapi.cx();
|
|
JS::RootedValue jsval(cx);
|
|
|
|
nsresult rv = GetUntrustedModuleLoadEventsJSValue(mData, cx, &jsval);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mPromise->MaybeReject(rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
mPromise->MaybeResolve(jsval);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
class GetUntrustedModulesTelemetryDataRunnable final : public Runnable {
|
|
nsMainThreadPtrHandle<dom::Promise> mPromise;
|
|
|
|
public:
|
|
explicit GetUntrustedModulesTelemetryDataRunnable(
|
|
const nsMainThreadPtrHandle<dom::Promise>& aPromise)
|
|
: Runnable("GetUntrustedModulesTelemetryDataRunnable"),
|
|
mPromise(aPromise) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
NS_IMETHOD
|
|
Run() override {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
RefPtr<DllServices> dllSvc(DllServices::Get());
|
|
UntrustedModuleLoadTelemetryData data;
|
|
bool ok = dllSvc->GetUntrustedModuleTelemetryData(data);
|
|
|
|
// Dispatch back to the main thread for remaining JS processing.
|
|
return NS_DispatchToMainThread(new GetUntrustedModulesMainThreadRunnable(
|
|
mPromise, ok, std::move(data)));
|
|
}
|
|
};
|
|
|
|
nsresult GetUntrustedModuleLoadEvents(JSContext* cx, dom::Promise** aPromise) {
|
|
// Create a promise using global context.
|
|
nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx);
|
|
if (NS_WARN_IF(!global)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
ErrorResult result;
|
|
RefPtr<dom::Promise> promise = dom::Promise::Create(global, result);
|
|
if (NS_WARN_IF(result.Failed())) {
|
|
return result.StealNSResult();
|
|
}
|
|
|
|
// Create a worker thread to perform the heavy work.
|
|
nsCOMPtr<nsIThread> workThread;
|
|
nsresult rv = NS_NewNamedThread("UntrustedDLLs", getter_AddRefs(workThread));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
promise->MaybeReject(NS_ERROR_FAILURE);
|
|
return NS_OK;
|
|
}
|
|
|
|
// In order to pass the promise through the worker thread to the main thread,
|
|
// this is needed.
|
|
nsMainThreadPtrHandle<dom::Promise> mainThreadPromise(
|
|
new nsMainThreadPtrHolder<dom::Promise>(
|
|
"Telemetry::UntrustedModuleLoadEvents::Promise", promise));
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new GetUntrustedModulesTelemetryDataRunnable(mainThreadPromise);
|
|
promise.forget(aPromise);
|
|
|
|
return workThread->Dispatch(runnable.forget(),
|
|
nsIEventTarget::DISPATCH_NORMAL);
|
|
}
|
|
|
|
} // namespace Telemetry
|
|
} // namespace mozilla
|