summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/other
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/other')
-rw-r--r--toolkit/components/telemetry/other/CombinedStacks.cpp257
-rw-r--r--toolkit/components/telemetry/other/CombinedStacks.h108
-rw-r--r--toolkit/components/telemetry/other/ProcessedStack.cpp188
-rw-r--r--toolkit/components/telemetry/other/ProcessedStack.h137
-rw-r--r--toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp182
-rw-r--r--toolkit/components/telemetry/other/TelemetryIOInterposeObserver.h116
-rw-r--r--toolkit/components/telemetry/other/UntrustedModules.cpp293
-rw-r--r--toolkit/components/telemetry/other/UntrustedModules.h31
-rw-r--r--toolkit/components/telemetry/other/UntrustedModulesBackupService.cpp97
-rw-r--r--toolkit/components/telemetry/other/UntrustedModulesBackupService.h73
-rw-r--r--toolkit/components/telemetry/other/UntrustedModulesDataSerializer.cpp609
-rw-r--r--toolkit/components/telemetry/other/UntrustedModulesDataSerializer.h84
12 files changed, 2175 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/other/CombinedStacks.cpp b/toolkit/components/telemetry/other/CombinedStacks.cpp
new file mode 100644
index 0000000000..61248f9b40
--- /dev/null
+++ b/toolkit/components/telemetry/other/CombinedStacks.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "CombinedStacks.h"
+
+#include "jsapi.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty
+#include "js/String.h"
+
+namespace mozilla::Telemetry {
+
+// The maximum number of chrome hangs stacks that we're keeping.
+const size_t kMaxChromeStacksKept = 50;
+
+CombinedStacks::CombinedStacks() : CombinedStacks(kMaxChromeStacksKept) {}
+
+CombinedStacks::CombinedStacks(size_t aMaxStacksCount)
+ : mNextIndex(0), mMaxStacksCount(aMaxStacksCount) {}
+
+size_t CombinedStacks::GetMaxStacksCount() const { return mMaxStacksCount; }
+size_t CombinedStacks::GetModuleCount() const { return mModules.size(); }
+
+const Telemetry::ProcessedStack::Module& CombinedStacks::GetModule(
+ unsigned aIndex) const {
+ return mModules[aIndex];
+}
+
+void CombinedStacks::AddFrame(
+ size_t aStackIndex, const ProcessedStack::Frame& aFrame,
+ const std::function<const ProcessedStack::Module&(int)>& aModuleGetter) {
+ uint16_t modIndex;
+ if (aFrame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+ modIndex = aFrame.mModIndex;
+ } else {
+ const ProcessedStack::Module& module = aModuleGetter(aFrame.mModIndex);
+ auto modIterator = std::find(mModules.begin(), mModules.end(), module);
+ if (modIterator == mModules.end()) {
+ mModules.push_back(module);
+ modIndex = mModules.size() - 1;
+ } else {
+ modIndex = modIterator - mModules.begin();
+ }
+ }
+ mStacks[aStackIndex].push_back(
+ ProcessedStack::Frame{aFrame.mOffset, modIndex});
+}
+
+size_t CombinedStacks::AddStack(const Telemetry::ProcessedStack& aStack) {
+ size_t index = mNextIndex;
+ // Advance the indices of the circular queue holding the stacks.
+ mNextIndex = (mNextIndex + 1) % mMaxStacksCount;
+ // Grow the vector up to the maximum size, if needed.
+ if (mStacks.size() < mMaxStacksCount) {
+ mStacks.resize(mStacks.size() + 1);
+ }
+
+ // Clear the old stack before set.
+ mStacks[index].clear();
+
+ size_t stackSize = aStack.GetStackSize();
+ for (size_t i = 0; i < stackSize; ++i) {
+ // Need to specify a return type in the following lambda,
+ // otherwise it's incorrectly deduced to be a non-reference type.
+ AddFrame(index, aStack.GetFrame(i),
+ [&aStack](int aIdx) -> const ProcessedStack::Module& {
+ return aStack.GetModule(aIdx);
+ });
+ }
+ return index;
+}
+
+void CombinedStacks::AddStacks(const CombinedStacks& aStacks) {
+ mStacks.resize(
+ std::min(mStacks.size() + aStacks.GetStackCount(), mMaxStacksCount));
+
+ for (const auto& stack : aStacks.mStacks) {
+ size_t index = mNextIndex;
+ // Advance the indices of the circular queue holding the stacks.
+ mNextIndex = (mNextIndex + 1) % mMaxStacksCount;
+
+ // Clear the old stack before set.
+ mStacks[index].clear();
+
+ for (const auto& frame : stack) {
+ // Need to specify a return type in the following lambda,
+ // otherwise it's incorrectly deduced to be a non-reference type.
+ AddFrame(index, frame,
+ [&aStacks](int aIdx) -> const ProcessedStack::Module& {
+ return aStacks.mModules[aIdx];
+ });
+ }
+ }
+}
+
+const CombinedStacks::Stack& CombinedStacks::GetStack(unsigned aIndex) const {
+ return mStacks[aIndex];
+}
+
+size_t CombinedStacks::GetStackCount() const { return mStacks.size(); }
+
+size_t CombinedStacks::SizeOfExcludingThis() const {
+ // This is a crude approximation. We would like to do something like
+ // aMallocSizeOf(&mModules[0]), but on linux aMallocSizeOf will call
+ // malloc_usable_size which is only safe on the pointers returned by malloc.
+ // While it works on current libstdc++, it is better to be safe and not assume
+ // that &vec[0] points to one. We could use a custom allocator, but
+ // it doesn't seem worth it.
+ size_t n = 0;
+ n += mModules.capacity() * sizeof(Telemetry::ProcessedStack::Module);
+ n += mStacks.capacity() * sizeof(Stack);
+ for (const auto& s : mStacks) {
+ n += s.capacity() * sizeof(Telemetry::ProcessedStack::Frame);
+ }
+ return n;
+}
+
+void CombinedStacks::RemoveStack(unsigned aIndex) {
+ MOZ_ASSERT(aIndex < mStacks.size());
+
+ mStacks.erase(mStacks.begin() + aIndex);
+
+ if (aIndex < mNextIndex) {
+ if (mNextIndex == 0) {
+ mNextIndex = mStacks.size();
+ } else {
+ mNextIndex--;
+ }
+ }
+
+ if (mNextIndex > mStacks.size()) {
+ mNextIndex = mStacks.size();
+ }
+}
+
+void CombinedStacks::Swap(CombinedStacks& aOther) {
+ mModules.swap(aOther.mModules);
+ mStacks.swap(aOther.mStacks);
+
+ size_t nextIndex = aOther.mNextIndex;
+ aOther.mNextIndex = mNextIndex;
+ mNextIndex = nextIndex;
+
+ size_t maxStacksCount = aOther.mMaxStacksCount;
+ aOther.mMaxStacksCount = mMaxStacksCount;
+ mMaxStacksCount = maxStacksCount;
+}
+
+void CombinedStacks::Clear() {
+ mNextIndex = 0;
+ mStacks.clear();
+ mModules.clear();
+}
+
+JSObject* CreateJSStackObject(JSContext* cx, const CombinedStacks& stacks) {
+ JS::Rooted<JSObject*> ret(cx, JS_NewPlainObject(cx));
+ if (!ret) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> moduleArray(cx, JS::NewArrayObject(cx, 0));
+ if (!moduleArray) {
+ return nullptr;
+ }
+ bool ok =
+ JS_DefineProperty(cx, ret, "memoryMap", moduleArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return nullptr;
+ }
+
+ const size_t moduleCount = stacks.GetModuleCount();
+ for (size_t moduleIndex = 0; moduleIndex < moduleCount; ++moduleIndex) {
+ // Current module
+ const Telemetry::ProcessedStack::Module& module =
+ stacks.GetModule(moduleIndex);
+
+ JS::Rooted<JSObject*> moduleInfoArray(cx, JS::NewArrayObject(cx, 0));
+ if (!moduleInfoArray) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, moduleArray, moduleIndex, moduleInfoArray,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ unsigned index = 0;
+
+ // Module name
+ JS::Rooted<JSString*> str(cx, JS_NewUCStringCopyZ(cx, module.mName.get()));
+ if (!str || !JS_DefineElement(cx, moduleInfoArray, index++, str,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ // Module breakpad identifier
+ JS::Rooted<JSString*> id(cx,
+ JS_NewStringCopyZ(cx, module.mBreakpadId.get()));
+ if (!id ||
+ !JS_DefineElement(cx, moduleInfoArray, index, id, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ JS::Rooted<JSObject*> reportArray(cx, JS::NewArrayObject(cx, 0));
+ if (!reportArray) {
+ return nullptr;
+ }
+ ok = JS_DefineProperty(cx, ret, "stacks", reportArray, JSPROP_ENUMERATE);
+ if (!ok) {
+ return nullptr;
+ }
+
+ const size_t length = stacks.GetStackCount();
+ for (size_t i = 0; i < length; ++i) {
+ // Represent call stack PCs as (module index, offset) pairs.
+ JS::Rooted<JSObject*> pcArray(cx, JS::NewArrayObject(cx, 0));
+ if (!pcArray) {
+ return nullptr;
+ }
+
+ if (!JS_DefineElement(cx, reportArray, i, pcArray, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+
+ const CombinedStacks::Stack& stack = stacks.GetStack(i);
+ const uint32_t pcCount = stack.size();
+ for (size_t pcIndex = 0; pcIndex < pcCount; ++pcIndex) {
+ const Telemetry::ProcessedStack::Frame& frame = stack[pcIndex];
+ JS::Rooted<JSObject*> framePair(cx, JS::NewArrayObject(cx, 0));
+ if (!framePair) {
+ return nullptr;
+ }
+ int modIndex = (std::numeric_limits<uint16_t>::max() == frame.mModIndex)
+ ? -1
+ : frame.mModIndex;
+ if (!JS_DefineElement(cx, framePair, 0, modIndex, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, framePair, 1,
+ static_cast<double>(frame.mOffset),
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ if (!JS_DefineElement(cx, pcArray, pcIndex, framePair,
+ JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+ }
+
+ return ret;
+}
+
+} // namespace mozilla::Telemetry
diff --git a/toolkit/components/telemetry/other/CombinedStacks.h b/toolkit/components/telemetry/other/CombinedStacks.h
new file mode 100644
index 0000000000..6bd46823db
--- /dev/null
+++ b/toolkit/components/telemetry/other/CombinedStacks.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef CombinedStacks_h__
+#define CombinedStacks_h__
+
+#include <vector>
+
+#include "ipc/IPCMessageUtils.h"
+#include "ProcessedStack.h"
+
+class JSObject;
+struct JSContext;
+
+namespace mozilla {
+namespace Telemetry {
+
+/**
+ * This class is conceptually a list of ProcessedStack objects, but it
+ * represents them more efficiently by keeping a single global list of modules.
+ */
+class CombinedStacks {
+ public:
+ explicit CombinedStacks();
+ explicit CombinedStacks(size_t aMaxStacksCount);
+
+ CombinedStacks(CombinedStacks&&) = default;
+ CombinedStacks& operator=(CombinedStacks&&) = default;
+
+ void Swap(CombinedStacks& aOther);
+
+ typedef std::vector<Telemetry::ProcessedStack::Frame> Stack;
+ const Telemetry::ProcessedStack::Module& GetModule(unsigned aIndex) const;
+ size_t GetModuleCount() const;
+ const Stack& GetStack(unsigned aIndex) const;
+ size_t AddStack(const Telemetry::ProcessedStack& aStack);
+ void AddStacks(const CombinedStacks& aStacks);
+ size_t GetStackCount() const;
+ size_t SizeOfExcludingThis() const;
+ void RemoveStack(unsigned aIndex);
+ size_t GetMaxStacksCount() const;
+
+ /** Clears the contents of vectors and resets the index. */
+ void Clear();
+
+ private:
+ std::vector<Telemetry::ProcessedStack::Module> mModules;
+ // A circular buffer to hold the stacks.
+ std::vector<Stack> mStacks;
+ // The index of the next buffer element to write to in mStacks.
+ size_t mNextIndex;
+ // The maximum number of stacks to keep in the CombinedStacks object.
+ size_t mMaxStacksCount;
+
+ void AddFrame(
+ size_t aStackIndex, const ProcessedStack::Frame& aFrame,
+ const std::function<const ProcessedStack::Module&(int)>& aModuleGetter);
+
+ friend struct ::IPC::ParamTraits<CombinedStacks>;
+};
+
+/**
+ * Creates a JSON representation of given combined stacks object.
+ */
+JSObject* CreateJSStackObject(JSContext* cx, const CombinedStacks& stacks);
+
+} // namespace Telemetry
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::Telemetry::CombinedStacks> {
+ typedef mozilla::Telemetry::CombinedStacks paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mModules);
+ WriteParam(aWriter, aParam.mStacks);
+ WriteParam(aWriter, aParam.mNextIndex);
+ WriteParam(aWriter, aParam.mMaxStacksCount);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mModules)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mStacks)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mNextIndex)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mMaxStacksCount)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // CombinedStacks_h__
diff --git a/toolkit/components/telemetry/other/ProcessedStack.cpp b/toolkit/components/telemetry/other/ProcessedStack.cpp
new file mode 100644
index 0000000000..ce62826a05
--- /dev/null
+++ b/toolkit/components/telemetry/other/ProcessedStack.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "ProcessedStack.h"
+
+namespace {
+
+struct StackFrame {
+ uintptr_t mPC; // The program counter at this position in the call stack.
+ uint16_t mIndex; // The number of this frame in the call stack.
+ uint16_t mModIndex; // The index of module that has this program counter.
+};
+
+#ifdef MOZ_GECKO_PROFILER
+static bool CompareByPC(const StackFrame& a, const StackFrame& b) {
+ return a.mPC < b.mPC;
+}
+
+static bool CompareByIndex(const StackFrame& a, const StackFrame& b) {
+ return a.mIndex < b.mIndex;
+}
+#endif
+
+} // namespace
+
+namespace mozilla::Telemetry {
+
+const size_t kMaxChromeStackDepth = 50;
+
+ProcessedStack::ProcessedStack() = default;
+
+size_t ProcessedStack::GetStackSize() const { return mStack.size(); }
+
+size_t ProcessedStack::GetNumModules() const { return mModules.size(); }
+
+bool ProcessedStack::Module::operator==(const Module& aOther) const {
+ return mName == aOther.mName && mBreakpadId == aOther.mBreakpadId;
+}
+
+const ProcessedStack::Frame& ProcessedStack::GetFrame(unsigned aIndex) const {
+ MOZ_ASSERT(aIndex < mStack.size());
+ return mStack[aIndex];
+}
+
+void ProcessedStack::AddFrame(const Frame& aFrame) { mStack.push_back(aFrame); }
+
+const ProcessedStack::Module& ProcessedStack::GetModule(unsigned aIndex) const {
+ MOZ_ASSERT(aIndex < mModules.size());
+ return mModules[aIndex];
+}
+
+void ProcessedStack::AddModule(const Module& aModule) {
+ mModules.push_back(aModule);
+}
+
+void ProcessedStack::Clear() {
+ mModules.clear();
+ mStack.clear();
+}
+
+ProcessedStack GetStackAndModules(const std::vector<uintptr_t>& aPCs) {
+ return BatchProcessedStackGenerator().GetStackAndModules(aPCs);
+}
+
+BatchProcessedStackGenerator::BatchProcessedStackGenerator()
+#ifdef MOZ_GECKO_PROFILER
+ : mSortedRawModules(SharedLibraryInfo::GetInfoForSelf())
+#endif
+{
+#ifdef MOZ_GECKO_PROFILER
+ mSortedRawModules.SortByAddress();
+#endif
+}
+
+#ifndef MOZ_GECKO_PROFILER
+static ProcessedStack GetStackAndModulesInternal(
+ std::vector<StackFrame>& aRawStack) {
+#else
+static ProcessedStack GetStackAndModulesInternal(
+ std::vector<StackFrame>& aRawStack, SharedLibraryInfo& aSortedRawModules) {
+ SharedLibraryInfo rawModules(aSortedRawModules);
+ // Remove all modules not referenced by a PC on the stack
+ std::sort(aRawStack.begin(), aRawStack.end(), CompareByPC);
+
+ size_t moduleIndex = 0;
+ size_t stackIndex = 0;
+ size_t stackSize = aRawStack.size();
+
+ while (moduleIndex < rawModules.GetSize()) {
+ const SharedLibrary& module = rawModules.GetEntry(moduleIndex);
+ uintptr_t moduleStart = module.GetStart();
+ uintptr_t moduleEnd = module.GetEnd() - 1;
+ // the interval is [moduleStart, moduleEnd)
+
+ bool moduleReferenced = false;
+ for (; stackIndex < stackSize; ++stackIndex) {
+ uintptr_t pc = aRawStack[stackIndex].mPC;
+ if (pc >= moduleEnd) break;
+
+ if (pc >= moduleStart) {
+ // If the current PC is within the current module, mark
+ // module as used
+ moduleReferenced = true;
+ aRawStack[stackIndex].mPC -= moduleStart;
+ aRawStack[stackIndex].mModIndex = moduleIndex;
+ } else {
+ // PC does not belong to any module. It is probably from
+ // the JIT. Use a fixed mPC so that we don't get different
+ // stacks on different runs.
+ aRawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
+ }
+ }
+
+ if (moduleReferenced) {
+ ++moduleIndex;
+ } else {
+ // Remove module if no PCs within its address range
+ rawModules.RemoveEntries(moduleIndex, moduleIndex + 1);
+ }
+ }
+
+ for (; stackIndex < stackSize; ++stackIndex) {
+ // These PCs are past the last module.
+ aRawStack[stackIndex].mPC = std::numeric_limits<uintptr_t>::max();
+ }
+
+ std::sort(aRawStack.begin(), aRawStack.end(), CompareByIndex);
+#endif
+
+ // Copy the information to the return value.
+ ProcessedStack Ret;
+ for (auto& rawFrame : aRawStack) {
+ mozilla::Telemetry::ProcessedStack::Frame frame = {rawFrame.mPC,
+ rawFrame.mModIndex};
+ Ret.AddFrame(frame);
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
+ const SharedLibrary& info = rawModules.GetEntry(i);
+ mozilla::Telemetry::ProcessedStack::Module module = {info.GetDebugName(),
+ info.GetBreakpadId()};
+ Ret.AddModule(module);
+ }
+#endif
+
+ return Ret;
+}
+
+ProcessedStack BatchProcessedStackGenerator::GetStackAndModules(
+ const std::vector<uintptr_t>& aPCs) {
+ std::vector<StackFrame> rawStack;
+ auto stackEnd = aPCs.begin() + std::min(aPCs.size(), kMaxChromeStackDepth);
+ for (auto i = aPCs.begin(); i != stackEnd; ++i) {
+ uintptr_t aPC = *i;
+ StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
+ std::numeric_limits<uint16_t>::max()};
+ rawStack.push_back(Frame);
+ }
+
+#if defined(MOZ_GECKO_PROFILER)
+ return GetStackAndModulesInternal(rawStack, mSortedRawModules);
+#else
+ return GetStackAndModulesInternal(rawStack);
+#endif
+}
+
+ProcessedStack BatchProcessedStackGenerator::GetStackAndModules(
+ const uintptr_t* aBegin, const uintptr_t* aEnd) {
+ std::vector<StackFrame> rawStack;
+ for (auto i = aBegin; i != aEnd; ++i) {
+ uintptr_t aPC = *i;
+ StackFrame Frame = {aPC, static_cast<uint16_t>(rawStack.size()),
+ std::numeric_limits<uint16_t>::max()};
+ rawStack.push_back(Frame);
+ }
+
+#if defined(MOZ_GECKO_PROFILER)
+ return GetStackAndModulesInternal(rawStack, mSortedRawModules);
+#else
+ return GetStackAndModulesInternal(rawStack);
+#endif
+}
+
+} // namespace mozilla::Telemetry
diff --git a/toolkit/components/telemetry/other/ProcessedStack.h b/toolkit/components/telemetry/other/ProcessedStack.h
new file mode 100644
index 0000000000..e289fe3fe6
--- /dev/null
+++ b/toolkit/components/telemetry/other/ProcessedStack.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+#ifndef ProcessedStack_h__
+#define ProcessedStack_h__
+
+#include <vector>
+
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/Vector.h"
+#include "nsStringFwd.h"
+#if defined(MOZ_GECKO_PROFILER)
+# include "shared-libraries.h"
+#endif // MOZ_GECKO_PROFILER
+
+namespace mozilla {
+namespace Telemetry {
+
+// This class represents a stack trace and the modules referenced in that trace.
+// It is designed to be easy to read and write to disk or network and doesn't
+// include any logic on how to collect or read the information it stores.
+class ProcessedStack {
+ public:
+ ProcessedStack();
+ size_t GetStackSize() const;
+ size_t GetNumModules() const;
+
+ struct Frame {
+ // The offset of this program counter in its module or an absolute pc.
+ uintptr_t mOffset;
+ // The index to pass to GetModule to get the module this program counter
+ // was in.
+ uint16_t mModIndex;
+ };
+ struct Module {
+ // The file name, /foo/bar/libxul.so for example.
+ // It can contain unicode characters.
+ nsString mName;
+ nsCString mBreakpadId;
+
+ bool operator==(const Module& other) const;
+ };
+
+ const Frame& GetFrame(unsigned aIndex) const;
+ void AddFrame(const Frame& aFrame);
+ const Module& GetModule(unsigned aIndex) const;
+ void AddModule(const Module& aFrame);
+
+ void Clear();
+
+ private:
+ std::vector<Module> mModules;
+ std::vector<Frame> mStack;
+};
+
+// Get the current list of loaded modules, filter and pair it to the provided
+// stack. We let the caller collect the stack since different callers have
+// different needs (current thread X main thread, stopping the thread, etc).
+ProcessedStack GetStackAndModules(const std::vector<uintptr_t>& aPCs);
+
+// This class optimizes repeated calls to GetStackAndModules.
+class BatchProcessedStackGenerator {
+ public:
+ BatchProcessedStackGenerator();
+ ProcessedStack GetStackAndModules(const std::vector<uintptr_t>& aPCs);
+
+ template <typename AllocatorPolicy>
+ ProcessedStack GetStackAndModules(
+ const Vector<void*, 0, AllocatorPolicy>& aPCs) {
+ return GetStackAndModules(reinterpret_cast<const uintptr_t*>(aPCs.begin()),
+ reinterpret_cast<const uintptr_t*>(aPCs.end()));
+ }
+
+ private:
+ ProcessedStack GetStackAndModules(const uintptr_t* aBegin,
+ const uintptr_t* aEnd);
+#if defined(MOZ_GECKO_PROFILER)
+ SharedLibraryInfo mSortedRawModules;
+#endif
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::Telemetry::ProcessedStack::Module> {
+ typedef mozilla::Telemetry::ProcessedStack::Module paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mName);
+ WriteParam(aWriter, aParam.mBreakpadId);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mName)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mBreakpadId)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::Telemetry::ProcessedStack::Frame> {
+ typedef mozilla::Telemetry::ProcessedStack::Frame paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mOffset);
+ WriteParam(aWriter, aParam.mModIndex);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mOffset)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mModIndex)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif // ProcessedStack_h__
diff --git a/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp
new file mode 100644
index 0000000000..c0792bb48c
--- /dev/null
+++ b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "TelemetryIOInterposeObserver.h"
+#include "core/TelemetryCommon.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineUCProperty
+#include "js/PropertyDescriptor.h" // JSPROP_ENUMERATE, JSPROP_READONLY
+#include "js/ValueArray.h"
+#include "nsIFile.h"
+
+namespace mozilla::Telemetry {
+
+TelemetryIOInterposeObserver::TelemetryIOInterposeObserver(nsIFile* aXreDir)
+ : mCurStage(STAGE_STARTUP) {
+ nsAutoString xreDirPath;
+ nsresult rv = aXreDir->GetPath(xreDirPath);
+ if (NS_SUCCEEDED(rv)) {
+ AddPath(xreDirPath, u"{xre}"_ns);
+ }
+}
+
+void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath,
+ const nsAString& aSubstName) {
+ mSafeDirs.AppendElement(SafeDir(aPath, aSubstName));
+}
+
+// Threshold for reporting slow main-thread I/O (50 milliseconds).
+const TimeDuration kTelemetryReportThreshold =
+ TimeDuration::FromMilliseconds(50);
+
+void TelemetryIOInterposeObserver::Observe(Observation& aOb) {
+ // We only report main-thread I/O
+ if (!IsMainThread()) {
+ return;
+ }
+
+ if (aOb.ObservedOperation() == OpNextStage) {
+ mCurStage = NextStage(mCurStage);
+ MOZ_ASSERT(mCurStage < NUM_STAGES);
+ return;
+ }
+
+ if (aOb.Duration() < kTelemetryReportThreshold) {
+ return;
+ }
+
+ // Get the filename
+ nsAutoString filename;
+ aOb.Filename(filename);
+
+ // Discard observations without filename
+ if (filename.IsEmpty()) {
+ return;
+ }
+
+#if defined(XP_WIN)
+ auto comparator = nsCaseInsensitiveStringComparator;
+#else
+ auto comparator = nsTDefaultStringComparator<char16_t>;
+#endif
+ nsAutoString processedName;
+ uint32_t safeDirsLen = mSafeDirs.Length();
+ for (uint32_t i = 0; i < safeDirsLen; ++i) {
+ if (StringBeginsWith(filename, mSafeDirs[i].mPath, comparator)) {
+ processedName = mSafeDirs[i].mSubstName;
+ processedName += Substring(filename, mSafeDirs[i].mPath.Length());
+ break;
+ }
+ }
+
+ if (processedName.IsEmpty()) {
+ return;
+ }
+
+ // Create a new entry or retrieve the existing one
+ FileIOEntryType* entry = mFileStats.PutEntry(processedName);
+ if (entry) {
+ FileStats& stats = entry->GetModifiableData()->mStats[mCurStage];
+ // Update the statistics
+ stats.totalTime += (double)aOb.Duration().ToMilliseconds();
+ switch (aOb.ObservedOperation()) {
+ case OpCreateOrOpen:
+ stats.creates++;
+ break;
+ case OpRead:
+ stats.reads++;
+ break;
+ case OpWrite:
+ stats.writes++;
+ break;
+ case OpFSync:
+ stats.fsyncs++;
+ break;
+ case OpStat:
+ stats.stats++;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+bool TelemetryIOInterposeObserver::ReflectFileStats(FileIOEntryType* entry,
+ JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ JS::RootedValueArray<NUM_STAGES> stages(cx);
+
+ FileStatsByStage& statsByStage = *entry->GetModifiableData();
+ for (int s = STAGE_STARTUP; s < NUM_STAGES; ++s) {
+ FileStats& fileStats = statsByStage.mStats[s];
+
+ if (fileStats.totalTime == 0 && fileStats.creates == 0 &&
+ fileStats.reads == 0 && fileStats.writes == 0 &&
+ fileStats.fsyncs == 0 && fileStats.stats == 0) {
+ // Don't add an array that contains no information
+ stages[s].setNull();
+ continue;
+ }
+
+ // Array we want to report
+ JS::RootedValueArray<6> stats(cx);
+ stats[0].setNumber(fileStats.totalTime);
+ stats[1].setNumber(fileStats.creates);
+ stats[2].setNumber(fileStats.reads);
+ stats[3].setNumber(fileStats.writes);
+ stats[4].setNumber(fileStats.fsyncs);
+ stats[5].setNumber(fileStats.stats);
+
+ // Create jsStats as array of elements above
+ JS::Rooted<JSObject*> jsStats(cx, JS::NewArrayObject(cx, stats));
+ if (!jsStats) {
+ continue;
+ }
+
+ stages[s].setObject(*jsStats);
+ }
+
+ JS::Rooted<JSObject*> jsEntry(cx, JS::NewArrayObject(cx, stages));
+ if (!jsEntry) {
+ return false;
+ }
+
+ // Add jsEntry to top-level dictionary
+ const nsAString& key = entry->GetKey();
+ return JS_DefineUCProperty(cx, obj, key.Data(), key.Length(), jsEntry,
+ JSPROP_ENUMERATE | JSPROP_READONLY);
+}
+
+bool TelemetryIOInterposeObserver::ReflectIntoJS(
+ JSContext* cx, JS::Handle<JSObject*> rootObj) {
+ return mFileStats.ReflectIntoJS(ReflectFileStats, cx, rootObj);
+}
+
+/**
+ * Get size of hash table with file stats
+ */
+
+size_t TelemetryIOInterposeObserver::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t TelemetryIOInterposeObserver::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ size += mFileStats.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFileStats.ConstIter(); !iter.Done(); iter.Next()) {
+ size += iter.Get()->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ size += mSafeDirs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ uint32_t safeDirsLen = mSafeDirs.Length();
+ for (uint32_t i = 0; i < safeDirsLen; ++i) {
+ size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+} // namespace mozilla::Telemetry
diff --git a/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.h b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.h
new file mode 100644
index 0000000000..54e3caf9b4
--- /dev/null
+++ b/toolkit/components/telemetry/other/TelemetryIOInterposeObserver.h
@@ -0,0 +1,116 @@
+/* -*- 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/. */
+
+/**
+ * IOInterposeObserver recording statistics of main-thread I/O during execution,
+ * aimed at consumption by TelemetryImpl
+ */
+
+#ifndef TelemetryIOInterposeObserver_h__
+#define TelemetryIOInterposeObserver_h__
+
+#include "core/TelemetryCommon.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/IOInterposer.h"
+#include "nsBaseHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+class TelemetryIOInterposeObserver : public IOInterposeObserver {
+ /** File-level statistics structure */
+ struct FileStats {
+ FileStats()
+ : creates(0), reads(0), writes(0), fsyncs(0), stats(0), totalTime(0) {}
+ uint32_t creates; /** Number of create/open operations */
+ uint32_t reads; /** Number of read operations */
+ uint32_t writes; /** Number of write operations */
+ uint32_t fsyncs; /** Number of fsync operations */
+ uint32_t stats; /** Number of stat operations */
+ double totalTime; /** Accumulated duration of all operations */
+ };
+
+ struct SafeDir {
+ SafeDir(const nsAString& aPath, const nsAString& aSubstName)
+ : mPath(aPath), mSubstName(aSubstName) {}
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mPath.SizeOfExcludingThisIfUnshared(aMallocSizeOf) +
+ mSubstName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+ nsString mPath; /** Path to the directory */
+ nsString mSubstName; /** Name to substitute with */
+ };
+
+ public:
+ explicit TelemetryIOInterposeObserver(nsIFile* aXreDir);
+
+ /**
+ * An implementation of Observe that records statistics of all
+ * file IO operations.
+ */
+ void Observe(Observation& aOb) override;
+
+ /**
+ * Reflect recorded file IO statistics into Javascript
+ */
+ bool ReflectIntoJS(JSContext* cx, JS::Handle<JSObject*> rootObj);
+
+ /**
+ * Adds a path for inclusion in main thread I/O report.
+ * @param aPath Directory path
+ * @param aSubstName Name to substitute for aPath for privacy reasons
+ */
+ void AddPath(const nsAString& aPath, const nsAString& aSubstName);
+
+ /**
+ * Get size of hash table with file stats
+ */
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ enum Stage { STAGE_STARTUP = 0, STAGE_NORMAL, STAGE_SHUTDOWN, NUM_STAGES };
+ static inline Stage NextStage(Stage aStage) {
+ switch (aStage) {
+ case STAGE_STARTUP:
+ return STAGE_NORMAL;
+ case STAGE_NORMAL:
+ return STAGE_SHUTDOWN;
+ case STAGE_SHUTDOWN:
+ return STAGE_SHUTDOWN;
+ default:
+ return NUM_STAGES;
+ }
+ }
+
+ struct FileStatsByStage {
+ FileStats mStats[NUM_STAGES];
+ };
+ typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType;
+
+ // Statistics for each filename
+ Common::AutoHashtable<FileIOEntryType> mFileStats;
+ // Container for allowed directories
+ nsTArray<SafeDir> mSafeDirs;
+ Stage mCurStage;
+
+ /**
+ * Reflect a FileIOEntryType object to a Javascript property on obj with
+ * filename as key containing array:
+ * [totalTime, creates, reads, writes, fsyncs, stats]
+ */
+ static bool ReflectFileStats(FileIOEntryType* entry, JSContext* cx,
+ JS::Handle<JSObject*> obj);
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // TelemetryIOInterposeObserver_h__
diff --git a/toolkit/components/telemetry/other/UntrustedModules.cpp b/toolkit/components/telemetry/other/UntrustedModules.cpp
new file mode 100644
index 0000000000..fc988c2d06
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModules.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "mozilla/dom/ContentParent.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessManager.h"
+#include "mozilla/RDDChild.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/WinDllServices.h"
+#include "nsISupportsImpl.h"
+#include "nsProxyRelease.h"
+#include "nsXULAppAPI.h"
+#include "UntrustedModulesDataSerializer.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+static const uint32_t kMaxModulesArrayLen = 100;
+
+using UntrustedModulesIpcPromise =
+ MozPromise<Maybe<UntrustedModulesData>, ipc::ResponseRejectReason, true>;
+
+using MultiGetUntrustedModulesPromise =
+ MozPromise<bool /*aIgnored*/, nsresult, true>;
+
+class MOZ_HEAP_CLASS MultiGetUntrustedModulesData final {
+ public:
+ /**
+ * @param aFlags [in] Combinations of the flags defined under nsITelemetry.
+ * (See "Flags for getUntrustedModuleLoadEvents"
+ * in nsITelemetry.idl)
+ */
+ explicit MultiGetUntrustedModulesData(uint32_t aFlags)
+ : mFlags(aFlags),
+ mBackupSvc(UntrustedModulesBackupService::Get()),
+ mPromise(new MultiGetUntrustedModulesPromise::Private(__func__)),
+ mNumPending(0) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MultiGetUntrustedModulesData)
+
+ RefPtr<MultiGetUntrustedModulesPromise> GetUntrustedModuleLoadEvents();
+ void Serialize(RefPtr<dom::Promise>&& aPromise);
+
+ MultiGetUntrustedModulesData(const MultiGetUntrustedModulesData&) = delete;
+ MultiGetUntrustedModulesData(MultiGetUntrustedModulesData&&) = delete;
+ MultiGetUntrustedModulesData& operator=(const MultiGetUntrustedModulesData&) =
+ delete;
+ MultiGetUntrustedModulesData& operator=(MultiGetUntrustedModulesData&&) =
+ delete;
+
+ private:
+ ~MultiGetUntrustedModulesData() = default;
+
+ void AddPending(RefPtr<UntrustedModulesPromise>&& aNewPending) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ++mNumPending;
+
+ RefPtr<MultiGetUntrustedModulesData> self(this);
+ aNewPending->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](Maybe<UntrustedModulesData>&& aResult) {
+ self->OnCompletion(std::move(aResult));
+ },
+ [self](nsresult aReason) { self->OnCompletion(); });
+ }
+
+ void AddPending(RefPtr<UntrustedModulesIpcPromise>&& aNewPending) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ++mNumPending;
+
+ RefPtr<MultiGetUntrustedModulesData> self(this);
+ aNewPending->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](Maybe<UntrustedModulesData>&& aResult) {
+ self->OnCompletion(std::move(aResult));
+ },
+ [self](ipc::ResponseRejectReason&& aReason) { self->OnCompletion(); });
+ }
+
+ void OnCompletion() {
+ MOZ_ASSERT(NS_IsMainThread() && mNumPending > 0);
+
+ --mNumPending;
+ if (mNumPending) {
+ return;
+ }
+
+ mPromise->Resolve(true, __func__);
+ }
+
+ void OnCompletion(Maybe<UntrustedModulesData>&& aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aResult.isSome()) {
+ mBackupSvc->Backup(std::move(aResult.ref()));
+ }
+
+ OnCompletion();
+ }
+
+ private:
+ // Combinations of the flags defined under nsITelemetry.
+ // (See "Flags for getUntrustedModuleLoadEvents" in nsITelemetry.idl)
+ uint32_t mFlags;
+
+ RefPtr<UntrustedModulesBackupService> mBackupSvc;
+ RefPtr<MultiGetUntrustedModulesPromise::Private> mPromise;
+ size_t mNumPending;
+};
+
+RefPtr<MultiGetUntrustedModulesPromise>
+MultiGetUntrustedModulesData::GetUntrustedModuleLoadEvents() {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ // Parent process
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ AddPending(dllSvc->GetUntrustedModulesData());
+
+ // Child processes
+ nsTArray<dom::ContentParent*> contentParents;
+ dom::ContentParent::GetAll(contentParents);
+ for (auto&& contentParent : contentParents) {
+ AddPending(contentParent->SendGetUntrustedModulesData());
+ }
+
+ if (auto* socketActor = net::SocketProcessParent::GetSingleton()) {
+ AddPending(socketActor->SendGetUntrustedModulesData());
+ }
+
+ if (RDDProcessManager* rddMgr = RDDProcessManager::Get()) {
+ if (RDDChild* rddChild = rddMgr->GetRDDChild()) {
+ AddPending(rddChild->SendGetUntrustedModulesData());
+ }
+ }
+
+ if (RefPtr<ipc::UtilityProcessManager> utilityManager =
+ ipc::UtilityProcessManager::GetIfExists()) {
+ for (RefPtr<ipc::UtilityProcessParent>& parent :
+ utilityManager->GetAllProcessesProcessParent()) {
+ AddPending(parent->SendGetUntrustedModulesData());
+ }
+ }
+
+ return mPromise;
+}
+
+void MultiGetUntrustedModulesData::Serialize(RefPtr<dom::Promise>&& aPromise) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ dom::AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
+ aPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+ UntrustedModulesDataSerializer serializer(cx, kMaxModulesArrayLen, mFlags);
+ if (!serializer) {
+ aPromise->MaybeReject(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsresult rv;
+ if (mFlags & nsITelemetry::INCLUDE_OLD_LOADEVENTS) {
+ // When INCLUDE_OLD_LOADEVENTS is set, we need to return instances
+ // from both "Staging" and "Settled" backup.
+ if (mFlags & nsITelemetry::KEEP_LOADEVENTS_NEW) {
+ // When INCLUDE_OLD_LOADEVENTS and KEEP_LOADEVENTS_NEW are set, we need to
+ // return a JS object consisting of all instances from both "Staging" and
+ // "Settled" backups, keeping instances in those backups as is.
+ if (mFlags & nsITelemetry::EXCLUDE_STACKINFO_FROM_LOADEVENTS) {
+ // Without the stack info, we can add multiple UntrustedModulesData to
+ // the serializer directly.
+ rv = serializer.Add(mBackupSvc->Staging());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPromise->MaybeReject(rv);
+ return;
+ }
+ rv = serializer.Add(mBackupSvc->Settled());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPromise->MaybeReject(rv);
+ return;
+ }
+ } else {
+ // Currently we don't have a method to merge UntrustedModulesData into
+ // a serialized JS object because merging CombinedStack will be tricky.
+ // Thus we return an error on this flag combination.
+ aPromise->MaybeReject(NS_ERROR_INVALID_ARG);
+ return;
+ }
+ } else {
+ // When KEEP_LOADEVENTS_NEW is not set, we can move data from "Staging"
+ // to "Settled" first, then add "Settled" to the serializer.
+ mBackupSvc->SettleAllStagingData();
+
+ const UntrustedModulesBackupData& settledRef = mBackupSvc->Settled();
+ if (settledRef.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ rv = serializer.Add(settledRef);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPromise->MaybeReject(rv);
+ return;
+ }
+ }
+ } else {
+ // When INCLUDE_OLD_LOADEVENTS is not set, we serialize only the "Staging"
+ // into a JS object.
+ const UntrustedModulesBackupData& stagingRef = mBackupSvc->Staging();
+
+ if (stagingRef.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+
+ rv = serializer.Add(stagingRef);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPromise->MaybeReject(rv);
+ return;
+ }
+
+ // When KEEP_LOADEVENTS_NEW is not set, we move all "Staging" instances
+ // to the "Settled".
+ if (!(mFlags & nsITelemetry::KEEP_LOADEVENTS_NEW)) {
+ mBackupSvc->SettleAllStagingData();
+ }
+ }
+
+#if defined(XP_WIN)
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ nt::SharedSection* sharedSection = dllSvc->GetSharedSection();
+ if (sharedSection) {
+ auto dynamicBlocklist = sharedSection->GetDynamicBlocklist();
+
+ nsTArray<nsDependentSubstring> blockedModules;
+ for (const auto& blockedEntry : dynamicBlocklist) {
+ if (!blockedEntry.IsValidDynamicBlocklistEntry()) {
+ break;
+ }
+ blockedModules.AppendElement(
+ nsDependentSubstring(blockedEntry.mName.Buffer,
+ blockedEntry.mName.Length / sizeof(wchar_t)));
+ }
+ rv = serializer.AddBlockedModules(blockedModules);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aPromise->MaybeReject(rv);
+ return;
+ }
+ }
+#endif
+
+ JS::Rooted<JS::Value> jsval(cx);
+ serializer.GetObject(&jsval);
+ aPromise->MaybeResolve(jsval);
+}
+
+nsresult GetUntrustedModuleLoadEvents(uint32_t aFlags, 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();
+ }
+
+ auto multi = MakeRefPtr<MultiGetUntrustedModulesData>(aFlags);
+ multi->GetUntrustedModuleLoadEvents()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise, multi](bool) mutable { multi->Serialize(std::move(promise)); },
+ [promise](nsresult aRv) { promise->MaybeReject(aRv); });
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+} // namespace Telemetry
+} // namespace mozilla
diff --git a/toolkit/components/telemetry/other/UntrustedModules.h b/toolkit/components/telemetry/other/UntrustedModules.h
new file mode 100644
index 0000000000..3bb53c1f9a
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModules.h
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+#ifndef telemetry_UntrustedModules_h__
+#define telemetry_UntrustedModules_h__
+
+#include "jsapi.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+/**
+ * This function returns a promise that asynchronously processes and gathers
+ * untrusted module data. The promise is either resolved with the JS object
+ * ping payload, or is rejected upon failure.
+ *
+ * @param aFlags [in] Combinations of the flags defined under nsITelemetry.
+ * (See "Flags for getUntrustedModuleLoadEvents"
+ * in nsITelemetry.idl)
+ */
+nsresult GetUntrustedModuleLoadEvents(uint32_t aFlags, JSContext* cx,
+ dom::Promise** aPromise);
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // telemetry_UntrustedModules_h__
diff --git a/toolkit/components/telemetry/other/UntrustedModulesBackupService.cpp b/toolkit/components/telemetry/other/UntrustedModulesBackupService.cpp
new file mode 100644
index 0000000000..a0638e5aec
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModulesBackupService.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "UntrustedModulesBackupService.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticLocalPtr.h"
+
+namespace mozilla {
+
+ProcessHashKey::ProcessHashKey(GeckoProcessType aType, DWORD aPid)
+ : mType(aType), mPid(aPid) {}
+
+bool ProcessHashKey::operator==(const ProcessHashKey& aOther) const {
+ return mPid == aOther.mPid && mType == aOther.mType;
+}
+
+PLDHashNumber ProcessHashKey::Hash() const { return HashGeneric(mPid, mType); }
+
+void UntrustedModulesBackupData::Add(UntrustedModulesData&& aData) {
+ WithEntryHandle(
+ ProcessHashKey(aData.mProcessType, aData.mPid), [&](auto&& p) {
+ if (p) {
+ p.Data()->mData.Merge(std::move(aData));
+ } else {
+ p.Insert(MakeRefPtr<UntrustedModulesDataContainer>(std::move(aData)));
+ }
+ });
+}
+
+void UntrustedModulesBackupData::AddWithoutStacks(
+ UntrustedModulesData&& aData) {
+ WithEntryHandle(
+ ProcessHashKey(aData.mProcessType, aData.mPid), [&](auto&& p) {
+ if (p) {
+ p.Data()->mData.MergeWithoutStacks(std::move(aData));
+ } else {
+ aData.Truncate(true);
+ p.Insert(MakeRefPtr<UntrustedModulesDataContainer>(std::move(aData)));
+ }
+ });
+}
+
+/* static */
+UntrustedModulesBackupService* UntrustedModulesBackupService::Get() {
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ static StaticLocalRefPtr<UntrustedModulesBackupService> sInstance(
+ []() -> already_AddRefed<UntrustedModulesBackupService> {
+ RefPtr<UntrustedModulesBackupService> instance(
+ new UntrustedModulesBackupService());
+
+ auto setClearOnShutdown = [ptr = &sInstance]() -> void {
+ ClearOnShutdown(ptr);
+ };
+
+ if (NS_IsMainThread()) {
+ setClearOnShutdown();
+ return instance.forget();
+ }
+
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "mozilla::UntrustedModulesBackupService::Get",
+ std::move(setClearOnShutdown)));
+
+ return instance.forget();
+ }());
+
+ return sInstance;
+}
+
+void UntrustedModulesBackupService::Backup(UntrustedModulesData&& aData) {
+ mStaging.Add(std::move(aData));
+}
+
+void UntrustedModulesBackupService::SettleAllStagingData() {
+ UntrustedModulesBackupData staging;
+ staging.SwapElements(mStaging);
+
+ for (auto&& iter = staging.Iter(); !iter.Done(); iter.Next()) {
+ if (!iter.Data()) {
+ continue;
+ }
+ mSettled.AddWithoutStacks(std::move(iter.Data()->mData));
+ }
+}
+
+} // namespace mozilla
diff --git a/toolkit/components/telemetry/other/UntrustedModulesBackupService.h b/toolkit/components/telemetry/other/UntrustedModulesBackupService.h
new file mode 100644
index 0000000000..36e4cc7b1c
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModulesBackupService.h
@@ -0,0 +1,73 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UntrustedModulesBackupService_h
+#define mozilla_UntrustedModulesBackupService_h
+
+#include "mozilla/UntrustedModulesData.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+
+struct ProcessHashKey {
+ GeckoProcessType mType;
+ DWORD mPid;
+ ProcessHashKey(GeckoProcessType aType, DWORD aPid);
+ bool operator==(const ProcessHashKey& aOther) const;
+ PLDHashNumber Hash() const;
+};
+
+// UntrustedModulesData should not be refcounted as it's exchanged via IPC.
+// Instead, we define this container class owning UntrustedModulesData along
+// with a refcount.
+class MOZ_HEAP_CLASS UntrustedModulesDataContainer final {
+ ~UntrustedModulesDataContainer() = default;
+
+ public:
+ UntrustedModulesData mData;
+
+ explicit UntrustedModulesDataContainer(UntrustedModulesData&& aData)
+ : mData(std::move(aData)) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UntrustedModulesDataContainer)
+};
+
+class UntrustedModulesBackupData
+ : public nsRefPtrHashtable<nsGenericHashKey<ProcessHashKey>,
+ UntrustedModulesDataContainer> {
+ public:
+ void Add(UntrustedModulesData&& aData);
+ void AddWithoutStacks(UntrustedModulesData&& aData);
+};
+
+class MOZ_HEAP_CLASS UntrustedModulesBackupService final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UntrustedModulesBackupService)
+
+ static UntrustedModulesBackupService* Get();
+
+ // Back up data to mStaging
+ void Backup(UntrustedModulesData&& aData);
+
+ void SettleAllStagingData();
+
+ const UntrustedModulesBackupData& Staging() const { return mStaging; }
+ const UntrustedModulesBackupData& Settled() const { return mSettled; }
+
+ private:
+ // Data not yet submitted as telemetry
+ UntrustedModulesBackupData mStaging;
+
+ // Data already submitted as telemetry
+ // (This does not have stack information)
+ UntrustedModulesBackupData mSettled;
+
+ ~UntrustedModulesBackupService() = default;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_UntrustedModulesBackupService_h
diff --git a/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.cpp b/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.cpp
new file mode 100644
index 0000000000..603b85fd88
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.cpp
@@ -0,0 +1,609 @@
+/* -*- 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 "UntrustedModulesDataSerializer.h"
+
+#include "core/TelemetryCommon.h"
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_GetProperty
+#include "jsapi.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsITelemetry.h"
+#include "nsUnicharUtils.h"
+#include "nsXULAppAPI.h"
+
+#if defined(MOZ_GECKO_PROFILER)
+# include "shared-libraries.h"
+#endif // MOZ_GECKO_PROFILER
+
+namespace mozilla {
+namespace Telemetry {
+
+static const uint32_t kThirdPartyModulesPingVersion = 1;
+
+/**
+ * Limits the length of a string by removing the middle of the string, replacing
+ * with ellipsis.
+ * 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.
+ */
+static void LimitStringLength(nsAString& aStr, size_t aMaxFieldLength) {
+ if (aStr.Length() <= aMaxFieldLength) {
+ return;
+ }
+
+ constexpr auto kEllipsis = u"..."_ns;
+
+ if (aMaxFieldLength <= (kEllipsis.Length() + 3)) {
+ // An ellipsis is useless in this case, as it would obscure the string to
+ // the point that we cannot even determine the string's contents. We might
+ // as well just truncate.
+ aStr.Truncate(aMaxFieldLength);
+ return;
+ }
+
+ size_t cutPos = (aMaxFieldLength - kEllipsis.Length()) / 2;
+ size_t rightLen = aMaxFieldLength - kEllipsis.Length() - cutPos;
+ size_t cutLen = aStr.Length() - (cutPos + rightLen);
+
+ aStr.Replace(cutPos, cutLen, kEllipsis);
+}
+
+/**
+ * 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::Handle<JSObject*> aObj,
+ const char* aName, const nsAString& aVal,
+ size_t aMaxFieldLength = MAX_PATH) {
+ JS::Rooted<JS::Value> jsval(cx);
+ nsAutoString shortVal(aVal);
+ LimitStringLength(shortVal, aMaxFieldLength);
+ jsval.setString(Common::ToJSString(cx, shortVal));
+ return JS_DefineProperty(cx, aObj, aName, jsval, JSPROP_ENUMERATE);
+};
+
+static JSString* ModuleVersionToJSString(JSContext* aCx,
+ const ModuleVersion& aVersion) {
+ auto [major, minor, patch, build] = aVersion.AsTuple();
+
+ constexpr auto dot = u"."_ns;
+
+ nsAutoString strVer;
+ strVer.AppendInt(major);
+ strVer.Append(dot);
+ strVer.AppendInt(minor);
+ strVer.Append(dot);
+ strVer.AppendInt(patch);
+ strVer.Append(dot);
+ strVer.AppendInt(build);
+
+ return Common::ToJSString(aCx, strVer);
+}
+
+/**
+ * Convert the given container object 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 container to convert.
+ * @param aElementConverter [in] A callable used to convert each element
+ * to a JS element. The form of this function is:
+ * bool(JSContext *cx,
+ * JS::MutableHandleValue aRet,
+ * const ElementT& aElement)
+ * @return true if aRet was successfully assigned to the new array object.
+ */
+template <typename T, typename Converter, typename... Args>
+static bool ContainerToJSArray(JSContext* cx, JS::MutableHandle<JSObject*> aRet,
+ const T& aContainer,
+ Converter&& aElementConverter, Args&&... aArgs) {
+ JS::Rooted<JSObject*> arr(cx, JS::NewArrayObject(cx, 0));
+ if (!arr) {
+ return false;
+ }
+
+ size_t i = 0;
+ for (auto&& item : aContainer) {
+ JS::Rooted<JS::Value> jsel(cx);
+ if (!aElementConverter(cx, &jsel, *item, std::forward<Args>(aArgs)...)) {
+ return false;
+ }
+ if (!JS_DefineElement(cx, arr, i, jsel, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ ++i;
+ }
+
+ aRet.set(arr);
+ return true;
+}
+
+static bool SerializeModule(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aElement,
+ const RefPtr<ModuleRecord>& aModule,
+ uint32_t aFlags) {
+ if (!aModule) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return false;
+ }
+
+ if (aFlags & nsITelemetry::INCLUDE_PRIVATE_FIELDS_IN_LOADEVENTS) {
+ JS::Rooted<JS::Value> jsFileObj(aCx);
+ if (!dom::ToJSValue(aCx, aModule->mResolvedDosName, &jsFileObj) ||
+ !JS_DefineProperty(aCx, obj, "dllFile", jsFileObj, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ } else {
+ if (!AddLengthLimitedStringProp(aCx, obj, "resolvedDllName",
+ aModule->mSanitizedDllName)) {
+ return false;
+ }
+ }
+
+ if (aModule->mVersion.isSome()) {
+ JS::Rooted<JS::Value> jsModuleVersion(aCx);
+ jsModuleVersion.setString(
+ ModuleVersionToJSString(aCx, aModule->mVersion.ref()));
+ if (!JS_DefineProperty(aCx, obj, "fileVersion", jsModuleVersion,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+#if defined(MOZ_GECKO_PROFILER)
+ if (aModule->mResolvedDosName) {
+ nsAutoString path;
+ if (aModule->mResolvedDosName->GetPath(path) == NS_OK) {
+ SharedLibraryInfo info = SharedLibraryInfo::GetInfoFromPath(path.Data());
+ if (info.GetSize() > 0) {
+ nsCString breakpadId = info.GetEntry(0).GetBreakpadId();
+ if (!AddLengthLimitedStringProp(aCx, obj, "debugID",
+ NS_ConvertASCIItoUTF16(breakpadId))) {
+ return false;
+ }
+ }
+ }
+ }
+#endif // MOZ_GECKO_PROFILER
+
+ if (aModule->mVendorInfo.isSome()) {
+ const char* propName;
+
+ const VendorInfo& vendorInfo = aModule->mVendorInfo.ref();
+ switch (vendorInfo.mSource) {
+ case VendorInfo::Source::Signature:
+ propName = "signedBy";
+ break;
+ case VendorInfo::Source::VersionInfo:
+ propName = "companyName";
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown VendorInfo Source!");
+ return false;
+ }
+
+ MOZ_ASSERT(!vendorInfo.mVendor.IsEmpty());
+ if (vendorInfo.mVendor.IsEmpty()) {
+ return false;
+ }
+
+ if (!AddLengthLimitedStringProp(aCx, obj, propName, vendorInfo.mVendor)) {
+ return false;
+ }
+ }
+
+ JS::Rooted<JS::Value> jsTrustFlags(aCx);
+ jsTrustFlags.setNumber(static_cast<uint32_t>(aModule->mTrustFlags));
+ if (!JS_DefineProperty(aCx, obj, "trustFlags", jsTrustFlags,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ aElement.setObject(*obj);
+ return true;
+}
+
+/* static */
+bool UntrustedModulesDataSerializer::SerializeEvent(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aElement,
+ const ProcessedModuleLoadEventContainer& aEventContainer,
+ const IndexMap& aModuleIndices) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const ProcessedModuleLoadEvent& event = aEventContainer.mEvent;
+ if (!event) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+ if (!obj) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> jsProcessUptimeMS(aCx);
+ // Javascript doesn't like 64-bit integers; convert to double.
+ jsProcessUptimeMS.setNumber(static_cast<double>(event.mProcessUptimeMS));
+ if (!JS_DefineProperty(aCx, obj, "processUptimeMS", jsProcessUptimeMS,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (event.mLoadDurationMS) {
+ JS::Rooted<JS::Value> jsLoadDurationMS(aCx);
+ jsLoadDurationMS.setNumber(event.mLoadDurationMS.value());
+ if (!JS_DefineProperty(aCx, obj, "loadDurationMS", jsLoadDurationMS,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ JS::Rooted<JS::Value> jsThreadId(aCx);
+ jsThreadId.setNumber(static_cast<uint32_t>(event.mThreadId));
+ if (!JS_DefineProperty(aCx, obj, "threadID", jsThreadId, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ nsDependentCString effectiveThreadName;
+ if (event.mThreadId == ::GetCurrentThreadId()) {
+ effectiveThreadName.Rebind("Main Thread"_ns, 0);
+ } else {
+ effectiveThreadName.Rebind(event.mThreadName, 0);
+ }
+
+ if (!effectiveThreadName.IsEmpty()) {
+ JS::Rooted<JS::Value> jsThreadName(aCx);
+ jsThreadName.setString(Common::ToJSString(aCx, effectiveThreadName));
+ if (!JS_DefineProperty(aCx, obj, "threadName", jsThreadName,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ // Don't add this property unless mRequestedDllName differs from
+ // the associated module's mSanitizedDllName
+ if (!event.mRequestedDllName.IsEmpty() &&
+ !event.mRequestedDllName.Equals(event.mModule->mSanitizedDllName,
+ nsCaseInsensitiveStringComparator)) {
+ if (!AddLengthLimitedStringProp(aCx, obj, "requestedDllName",
+ event.mRequestedDllName)) {
+ return false;
+ }
+ }
+
+ nsAutoString strBaseAddress;
+ strBaseAddress.AppendLiteral(u"0x");
+ strBaseAddress.AppendInt(event.mBaseAddress, 16);
+
+ JS::Rooted<JS::Value> jsBaseAddress(aCx);
+ jsBaseAddress.setString(Common::ToJSString(aCx, strBaseAddress));
+ if (!JS_DefineProperty(aCx, obj, "baseAddress", jsBaseAddress,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ uint32_t index;
+ if (!aModuleIndices.Get(event.mModule->mResolvedNtName, &index)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> jsModuleIndex(aCx);
+ jsModuleIndex.setNumber(index);
+ if (!JS_DefineProperty(aCx, obj, "moduleIndex", jsModuleIndex,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> jsIsDependent(aCx);
+ jsIsDependent.setBoolean(event.mIsDependent);
+ if (!JS_DefineProperty(aCx, obj, "isDependent", jsIsDependent,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> jsLoadStatus(aCx);
+ jsLoadStatus.setNumber(event.mLoadStatus);
+ if (!JS_DefineProperty(aCx, obj, "loadStatus", jsLoadStatus,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ aElement.setObject(*obj);
+
+ return true;
+}
+
+static nsDependentCString GetProcessTypeString(GeckoProcessType aType) {
+ nsDependentCString strProcType;
+ if (aType == GeckoProcessType_Default) {
+ strProcType.Rebind("browser"_ns, 0);
+ } else {
+ strProcType.Rebind(XRE_GeckoProcessTypeToString(aType));
+ }
+ return strProcType;
+}
+
+nsresult UntrustedModulesDataSerializer::GetPerProcObject(
+ const UntrustedModulesData& aData, JS::MutableHandle<JSObject*> aObj) {
+ JS::Rooted<JS::Value> jsProcType(mCx);
+ jsProcType.setString(
+ Common::ToJSString(mCx, GetProcessTypeString(aData.mProcessType)));
+ if (!JS_DefineProperty(mCx, aObj, "processType", jsProcType,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> jsElapsed(mCx);
+ jsElapsed.setNumber(aData.mElapsed.ToSecondsSigDigits());
+ if (!JS_DefineProperty(mCx, aObj, "elapsed", jsElapsed, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aData.mXULLoadDurationMS.isSome()) {
+ JS::Rooted<JS::Value> jsXulLoadDurationMS(mCx);
+ jsXulLoadDurationMS.setNumber(aData.mXULLoadDurationMS.value());
+ if (!JS_DefineProperty(mCx, aObj, "xulLoadDurationMS", jsXulLoadDurationMS,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ JS::Rooted<JS::Value> jsSanitizationFailures(mCx);
+ jsSanitizationFailures.setNumber(aData.mSanitizationFailures);
+ if (!JS_DefineProperty(mCx, aObj, "sanitizationFailures",
+ jsSanitizationFailures, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> jsTrustTestFailures(mCx);
+ jsTrustTestFailures.setNumber(aData.mTrustTestFailures);
+ if (!JS_DefineProperty(mCx, aObj, "trustTestFailures", jsTrustTestFailures,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> eventsArray(mCx);
+ if (!ContainerToJSArray(mCx, &eventsArray, aData.mEvents, &SerializeEvent,
+ mIndexMap)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!JS_DefineProperty(mCx, aObj, "events", eventsArray, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!(mFlags & nsITelemetry::EXCLUDE_STACKINFO_FROM_LOADEVENTS)) {
+ JS::Rooted<JSObject*> combinedStacksObj(
+ mCx, CreateJSStackObject(mCx, aData.mStacks));
+ if (!combinedStacksObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!JS_DefineProperty(mCx, aObj, "combinedStacks", combinedStacksObj,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult UntrustedModulesDataSerializer::AddLoadEvents(
+ const UntrustedModuleLoadingEvents& aEvents,
+ JS::MutableHandle<JSObject*> aPerProcObj) {
+ JS::Rooted<JS::Value> eventsArrayVal(mCx);
+ if (!JS_GetProperty(mCx, aPerProcObj, "events", &eventsArrayVal) ||
+ !eventsArrayVal.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> eventsArray(mCx, &eventsArrayVal.toObject());
+ bool isArray;
+ if (!JS::IsArrayObject(mCx, eventsArray, &isArray) && !isArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t currentPos;
+ if (!GetArrayLength(mCx, eventsArray, &currentPos)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (auto item : aEvents) {
+ JS::Rooted<JS::Value> jsel(mCx);
+ if (!SerializeEvent(mCx, &jsel, *item, mIndexMap) ||
+ !JS_DefineElement(mCx, eventsArray, currentPos++, jsel,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult UntrustedModulesDataSerializer::AddSingleData(
+ const UntrustedModulesData& aData) {
+ // Serialize each entry in the modules hashtable out to the "modules" array
+ // and store the indices in |mIndexMap|
+ for (const auto& entry : aData.mModules) {
+ if (!mIndexMap.WithEntryHandle(entry.GetKey(), [&](auto&& addPtr) {
+ if (!addPtr) {
+ addPtr.Insert(mCurModulesArrayIdx);
+
+ JS::Rooted<JS::Value> jsModule(mCx);
+ if (!SerializeModule(mCx, &jsModule, entry.GetData(), mFlags) ||
+ !JS_DefineElement(mCx, mModulesArray, mCurModulesArrayIdx,
+ jsModule, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ ++mCurModulesArrayIdx;
+ }
+ return true;
+ })) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mCurModulesArrayIdx >= mMaxModulesArrayLen) {
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ nsAutoCString strPid;
+ strPid.Append(GetProcessTypeString(aData.mProcessType));
+ strPid.AppendLiteral(".0x");
+ strPid.AppendInt(static_cast<uint32_t>(aData.mPid), 16);
+
+ if (mFlags & nsITelemetry::EXCLUDE_STACKINFO_FROM_LOADEVENTS) {
+ JS::Rooted<JS::Value> perProcVal(mCx);
+ if (JS_GetProperty(mCx, mPerProcObjContainer, strPid.get(), &perProcVal) &&
+ perProcVal.isObject()) {
+ // If a corresponding per-proc object already exists in the dictionary,
+ // and we skip to serialize CombinedStacks, we can add loading events
+ // into the JS object directly.
+ JS::Rooted<JSObject*> perProcObj(mCx, &perProcVal.toObject());
+ return AddLoadEvents(aData.mEvents, &perProcObj);
+ }
+ }
+
+ JS::Rooted<JSObject*> perProcObj(mCx, JS_NewPlainObject(mCx));
+ if (!perProcObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = GetPerProcObject(aData, &perProcObj);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ JS::Rooted<JS::Value> jsPerProcObjValue(mCx);
+ jsPerProcObjValue.setObject(*perProcObj);
+ if (!JS_DefineProperty(mCx, mPerProcObjContainer, strPid.get(),
+ jsPerProcObjValue, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+UntrustedModulesDataSerializer::UntrustedModulesDataSerializer(
+ JSContext* aCx, uint32_t aMaxModulesArrayLen, uint32_t aFlags)
+ : mCtorResult(NS_ERROR_FAILURE),
+ mCx(aCx),
+ mMainObj(mCx, JS_NewPlainObject(mCx)),
+ mModulesArray(mCx, JS::NewArrayObject(mCx, 0)),
+ mBlockedModulesArray(mCx, JS::NewArrayObject(mCx, 0)),
+ mPerProcObjContainer(mCx, JS_NewPlainObject(mCx)),
+ mMaxModulesArrayLen(aMaxModulesArrayLen),
+ mCurModulesArrayIdx(0),
+ mCurBlockedModulesArrayIdx(0),
+ mFlags(aFlags) {
+ if (!mMainObj || !mModulesArray || !mBlockedModulesArray ||
+ !mPerProcObjContainer) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> jsVersion(mCx);
+ jsVersion.setNumber(kThirdPartyModulesPingVersion);
+ if (!JS_DefineProperty(mCx, mMainObj, "structVersion", jsVersion,
+ JSPROP_ENUMERATE)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> jsModulesArrayValue(mCx);
+ jsModulesArrayValue.setObject(*mModulesArray);
+ if (!JS_DefineProperty(mCx, mMainObj, "modules", jsModulesArrayValue,
+ JSPROP_ENUMERATE)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> jsBlockedModulesArrayValue(mCx);
+ jsBlockedModulesArrayValue.setObject(*mBlockedModulesArray);
+ if (!JS_DefineProperty(mCx, mMainObj, "blockedModules",
+ jsBlockedModulesArrayValue, JSPROP_ENUMERATE)) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> jsPerProcObjContainerValue(mCx);
+ jsPerProcObjContainerValue.setObject(*mPerProcObjContainer);
+ if (!JS_DefineProperty(mCx, mMainObj, "processes", jsPerProcObjContainerValue,
+ JSPROP_ENUMERATE)) {
+ return;
+ }
+
+ mCtorResult = NS_OK;
+}
+
+UntrustedModulesDataSerializer::operator bool() const {
+ return NS_SUCCEEDED(mCtorResult);
+}
+
+void UntrustedModulesDataSerializer::GetObject(
+ JS::MutableHandle<JS::Value> aRet) {
+ aRet.setObject(*mMainObj);
+}
+
+nsresult UntrustedModulesDataSerializer::Add(
+ const UntrustedModulesBackupData& aData) {
+ if (NS_FAILED(mCtorResult)) {
+ return mCtorResult;
+ }
+
+ for (const RefPtr<UntrustedModulesDataContainer>& container :
+ aData.Values()) {
+ if (!container) {
+ continue;
+ }
+
+ nsresult rv = AddSingleData(container->mData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult UntrustedModulesDataSerializer::AddBlockedModules(
+ const nsTArray<nsDependentSubstring>& blockedModules) {
+ if (NS_FAILED(mCtorResult)) {
+ return mCtorResult;
+ }
+
+ if (blockedModules.Length() >= mMaxModulesArrayLen) {
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+ }
+
+ for (const auto& blockedModule : blockedModules) {
+ JS::Rooted<JS::Value> jsBlockedModule(mCx);
+ jsBlockedModule.setString(Common::ToJSString(mCx, blockedModule));
+ if (!JS_DefineElement(mCx, mBlockedModulesArray, mCurBlockedModulesArrayIdx,
+ jsBlockedModule, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ ++mCurBlockedModulesArrayIdx;
+ }
+
+ return NS_OK;
+}
+
+} // namespace Telemetry
+} // namespace mozilla
diff --git a/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.h b/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.h
new file mode 100644
index 0000000000..cb0bce18ee
--- /dev/null
+++ b/toolkit/components/telemetry/other/UntrustedModulesDataSerializer.h
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+#ifndef UntrustedModulesDataSerializer_h__
+#define UntrustedModulesDataSerializer_h__
+
+#include "js/TypeDecls.h"
+#include "mozilla/UntrustedModulesData.h"
+#include "mozilla/Vector.h"
+#include "nsTHashMap.h"
+#include "UntrustedModulesBackupService.h"
+
+namespace mozilla {
+namespace Telemetry {
+
+// This class owns a JS object and serializes a given UntrustedModulesData
+// into it. Because this class uses JSAPI, an AutoJSAPI instance must be
+// on the stack before instantiating the class.
+class MOZ_RAII UntrustedModulesDataSerializer final {
+ using IndexMap = nsTHashMap<nsStringHashKey, uint32_t>;
+
+ nsresult mCtorResult;
+ JSContext* mCx;
+ JS::Rooted<JSObject*> mMainObj;
+ JS::Rooted<JSObject*> mModulesArray;
+ JS::Rooted<JSObject*> mBlockedModulesArray;
+ JS::Rooted<JSObject*> mPerProcObjContainer;
+
+ IndexMap mIndexMap;
+ const uint32_t mMaxModulesArrayLen;
+ uint32_t mCurModulesArrayIdx;
+ uint32_t mCurBlockedModulesArrayIdx;
+
+ // Combinations of the flags defined under nsITelemetry.
+ // (See "Flags for getUntrustedModuleLoadEvents" in nsITelemetry.idl)
+ const uint32_t mFlags;
+
+ static bool SerializeEvent(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aElement,
+ const ProcessedModuleLoadEventContainer& aEventContainer,
+ const IndexMap& aModuleIndices);
+ nsresult GetPerProcObject(const UntrustedModulesData& aData,
+ JS::MutableHandle<JSObject*> aObj);
+ nsresult AddLoadEvents(const UntrustedModuleLoadingEvents& aEvents,
+ JS::MutableHandle<JSObject*> aPerProcObj);
+ nsresult AddSingleData(const UntrustedModulesData& aData);
+
+ public:
+ UntrustedModulesDataSerializer(JSContext* aCx, uint32_t aMaxModulesArrayLen,
+ uint32_t aFlags = 0);
+ explicit operator bool() const;
+
+ /**
+ * Retrieves the JS object.
+ *
+ * @param aRet [out] This gets assigned to the newly created object.
+ */
+ void GetObject(JS::MutableHandle<JS::Value> aRet);
+
+ /**
+ * Adds data to the JS object.
+ *
+ * When the process of a given UntrustedModulesData collides with a key in
+ * the JS object, the entire UntrustedModulesData instance in the JS object
+ * will be replaced unless EXCLUDE_STACKINFO_FROM_LOADEVENTS is set.
+ * When EXCLUDE_STACKINFO_FROM_LOADEVENTS is set, the given loading events
+ * are appended into the JS object, keeping the existing data as is.
+ *
+ * @param aData [in] The source objects to add.
+ * @return nsresult
+ */
+ nsresult Add(const UntrustedModulesBackupData& aData);
+
+ nsresult AddBlockedModules(
+ const nsTArray<nsDependentSubstring>& blockedModules);
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+#endif // UntrustedModulesDataSerializer_h__