fune/toolkit/components/telemetry/other/UntrustedModules.cpp
Boris Zbarsky 1f3596354d Bug 1547923 part 2. Remove Promise::GlobalJSObject. r=mccr8
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
2019-05-02 18:38:04 +00:00

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